C# 繼承、接口與多態

C# 繼承、接口與多態

  我在這裏想談一談在C#中的繼承(繼承於基類或接口)和多態(如方法的覆寫)。繼承和多態是面向對象編程中兩個重要的特性,這種特性和編程語言本身是沒多大關係的,因此我先會用非編程的思維來談一談我對它們的認識,然後再談一談它們在C#中的實現方法。

  1、繼承的含義

  所謂繼承,就是“站在巨人的肩膀上”進行擴展。例如,最開始的鉛筆尾端是沒有橡皮擦的,後來有個男孩在尾端裝上了橡皮擦,鉛筆的銷量一路飆升,他也成了百萬富翁,這說明好的靈感多麼的重要!(偏題了啊喂!感興趣的童鞋請戳:http://baike.baidu.com/view/1628703.htm)好吧,迴歸正題,這就是一個典型的繼承。我們在做一個“橡皮頭鉛筆”時,不需要想如何做一支鉛筆,而是拿來一支現成的鉛筆,在它上面裝上橡皮而已。繼承可以幫助我們重用組件,繼承是人類不斷進步的源泉(貌似又偏題了!)。

  2、多態的含義

  從非編程的角度來看“多態”,可以理解成“不同的人執行相同的條例,卻有不同的行爲”。在編程的角度來看,是“不同的對象執行相同的方法,卻有不同的行爲”。怎麼來理解呢,我可以打以下這個比方。

  某學校的學生規範中有寫道:“老師進教室時,學生坐好如下課前準備:安靜坐好。此時值日生應該上講臺擦黑板,課代表收本科目作業。”教室裏,除了老師外,其他的人都是學生(包括課代表和值日生),在執行這些行爲的時候,課代表和值日生爲什麼不和規範中所提到的“學生”一樣安靜坐好呢?因爲規範中還說到,他們有自己的職責:一個應當擦黑板,一個應當收作業。這就是多態,雖然學生們都要做好課前準備,但是卻不全部相同。由“學生”派生出來的“課代表”要收作業,由“學生”派生出來的值日生要擦黑板,而“學生”這個“基類”的各個同學只需要安靜坐好。他們都執行着相同的條例(做課前準備),卻有不同的行爲。

  3、接口的含義

  通俗地來打比方,我要開發一個多功能插線板,其中的插頭孔要支持大陸標準、香港標準、英國標準和美國標準。我只需要把插線板按照標準挖出這4個孔就可以了,我就可以說,我這個插線板可以插大陸、香港、美國、英國的電源。至於插上去後怎麼樣,我不關心,我只關心,它可以插這些電源。再例如,我開發了一個新的電子產品,此產品要支持用USB來傳數據,我就要預留一個USB接口,至於接上USB數據線之後怎麼樣,這個是以後的事情,總之它先要支持USB接口。

  從程序的角度通俗的來理解接口,就是定義程序有這樣的一種“兼容性”,而不去管接口內部是怎麼實現的。因此,作爲接口,它只包含定義,而不包含具體的實現,這也是它和抽象類的最大區別(抽象類中可以包含方法的實現)。

  4、C#中的繼承與多態

  C#不像C++那樣區分“共有繼承"、"私有繼承"。所有的繼承均是共有的,也就是基類的所有成員的訪問性在派生類中都不會變。並且,C#中只支持單一繼承,但是可以繼承多個接口。這個很容易理解,一輛高級汽車,它只繼承於一個對象(一輛普通汽車),而不會同時繼承於一輛汽車和一架飛機;但是它可以用很多個接口,如加油的接口和充電的接口。Java和C#的設計值發現多重繼承在使用起來太蛋疼了,因此在他們的語言中去掉了這個特性。

  類的訪問性、成員定義方法我就不進行闡述了,因爲這是很基本的東西。下面我主要來談一談抽象類、虛函數,它們是實現多態的重要部分。

  有如下代碼,定義了一個Creature類,Creature類派生了兩個子類:Animal和Human。Creature類有一個抽象方法Eat。抽象方法是指,繼承於本抽象類的派生類都必須自己實現這個方法。抽象方法只是一個聲明,沒有方法體,包含抽象方法的類一定要爲抽象類。由於Eat是抽象方法,Human和Animal繼承於抽象類Creature,因此它們都必須要實現自己的Eat方法。代碼如下:

using System;

namespace Demo
{
    public abstract class Creature
    {
        public abstract void Eat();
    }

    public class Human : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("用火先將食物烤熟再喫。");
        }
    }

    public class Animal : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("直接吞下肚子。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Creature h = new Human();
            Creature a = new Animal();
            h.Eat();
            a.Eat();
        }
    }
}

  運行後,屏幕輸出如下:

  用火先將食物烤熟再喫。
  直接吞下肚子。

  顯然,前者是執行h.Eat()的結果,後者是執行a.Eat()的結果。派生於同一個類的兩個派生類,其Eat方法經過了重寫表現出了不同的行爲。需要注意的是,在覆寫基類的抽象方法時,必須要使用override關鍵字,否則無法通過編譯。

  抽象類Creature是無法實例化的(無法用new Creature()來實例化),因爲它還有一個方法Eat沒有實現。如果我們想讓Creature有個“默認”的Eat方法,應該怎麼做呢?這個時候就需要利用虛函數了。

  代碼如下:

using System;

namespace Demo
{
    public class Creature
    {
        public virtual void Eat()
        {
            Console.WriteLine("生物消化了食物。");
        }
    }

    public class Human : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("用火先將食物烤熟再喫。");
        }
    }

    public class Animal : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("直接吞下肚子。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Creature h = new Human();
            Creature a = new Animal();
            Creature c = new Creature();
            h.Eat();
            a.Eat();
            c.Eat();
        }
    }
}
  上例中,我們爲Creature類添加了一個虛方法Eat。也就是說,如果Creature的派生類沒有被覆寫,它就調用基類的Eat。所謂被覆寫,就是派生類中存在同名函數,且使用了關鍵字override。在之前提過的學生、課代表、值日生的例子中,代碼如下所示:

using System;

namespace Demo
{
    public class Student
    {
        public virtual void ClassBegin()
        {
            Console.WriteLine("安靜坐好。");
        }
    }

    public class StudentOnDuty : Student
    {
        public override void ClassBegin()
        {
            Console.WriteLine("擦黑板。");
        }
    }

    public class Representative : Student
    {
        public override void ClassBegin()
        {
            Console.WriteLine("收作業。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            //定義學生
            Student s = new Student();
            //定義課代表
            Student r = new Representative();
            //定義值日生
            Student d = new StudentOnDuty();
            s.ClassBegin();
            r.ClassBegin();
            d.ClassBegin();
        }
    }
}

  你可能會注意到,只有我們用基類的類型來定義一個派生類的對象時,纔有多態的機制。多態有什麼意義呢?舉例來說,假設有一個方法,需要傳入一個Student類,它可以調用Student類中的ClassBegin。如果沒有多態,我們就要這樣做:

public void WhenClassBegin(Student s)
{
    s.ClassBegin();
}

public void WhenClassBegin(Representative s)
{
    s.ClassBegin();
}

public void WhenClassBegin(StudentOnDuty s)
{
    s.ClassBegin();
}

  如果再增加一個派生類,我們就要爲WhenClassBegin多增加一個版本,這樣做工作量巨大而且不易於維護!現在有了多態,有了虛函數,我們只需要這樣來寫:

public void WhenClassBegin(Student s)
{
    s.ClassBegin();
}

  因爲Student對象能知道,它究竟要調用哪個ClassBegin方法。


  5、C#中的接口

   C#中定義接口十分簡單。接口中的方法不包含可訪問性修飾符,也不包含方法體。如以下就是一個接口的聲明和使用:

using System;

namespace Demo
{
    public interface IEdible
    {
        void Eat();
    }

    public class Program : IEdible
    {
        public static void Main(string[] args)
        {
            IEdible food = new Program();
            food.Eat();
        }

        public void Eat()
        {
            Console.WriteLine("Yummy!");
        }
    }
}

  需要注意的是IEdible food = new Program()這句話的意思是,實例化一個繼承了IEdible的對象。一定要注意,接口是不能實例化的,只能實例化一個具有某種接口的對象。如果Program沒有繼承IEdible,那麼編譯器就會報錯。

  這個就是我對繼承、接口、多態的一些理解,這些概念廣泛地運用在各種面向對象編程中,希望對大家有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章