C#基礎知識梳理系列六:抽象類與接口


摘 要

抽象類,是一種特殊的類,可以定義具有實現的方法,也可以定義未實現的方法契約,本身不能被實例化,只能在派生類中進行實例化。接口,對一組方法簽名進行統一的命名,只能定義未實現的方法契約,本身也不能被實例化,只能在實現類中進行實例化。二者都可以有部分數據成員(如:屬性),它們貌似有着相同的“契約”功能,但對各自的派生類(實現類)又有着不同的要求,那么,到底它們有何異同呢?這一章將從四個方面來講解它們的相同與不同之處。

第一節 定義

抽象類 不能實例化。抽象類的用途是提供多個派生類可共享的基類的公共定義,是對類進行抽象,可以有實現,也可以不實現。使用關鍵字abstract進行定義。如下定義一個抽象類:

public abstract class Code_06_03
{
}

再來看一下編譯器為它生成的IL:

.class public abstract auto ansi beforefieldinit ConsoleApp.Example06.Code_06_03
       extends [mscorlib]System.Object
{
} // end of class ConsoleApp.Example06.Code_06_03

可以看以,抽象類實際上是繼承了System.Object類,並且編譯器為它生成了一個默認的構造函數。

接口 它是對一組方法簽名進行統一命名,是對一組行為規范的定義,各個行為(方法)之間相互疏。使用關鍵字interface進行定義,如下定義一個接口:

public interface ICode_06_01
{
}

再來看一下編譯器的工作:

.class interface public abstract auto ansi ConsoleApp.Example06.ICode_06_01
{
} // end of class ConsoleApp.Example06.ICode_06_01

可以看到,接口實際上是把它當成抽象類來看待,但是沒有構造函數。

無論是抽象類擁有構造函數,還是接口不擁有構造函數,它們都是不能被實例化的。

 

第二節 成員的區別

抽象類:描述

1) 可以定義抽象方法,抽象方法沒有具體實現,僅僅是一個方法的契約,在子類中重寫該方法。抽象類可以重寫父類的虛方法為抽象方法。

2) 可以定義非抽象方法,但要求該方法要有具體實現,如果該方法是虛方法,則在子類中可以重寫該方法。

3) 可以定義字段,屬性,抽象屬性,事件及靜態成員。

如下是對類Code_06_03的擴充:

View Code
public abstract class Code_06_03
    {
        Dictionary<Guid, string> Root = new Dictionary<Guid, string>();
        public string Sex { get; set; }
        public abstract string Address { get; }
        public abstract int Add(int a, int b);
        protected virtual string GetAddress(string addressID)
        {
            return addressID + " 北京";
        }

        public void AddRoot(Guid id, string rootName)
        {
            this.Root.Add(id, rootName);
            OnAddRoot();
        }
        public event EventHandler AddRootEvent;
        void OnAddRoot()
        {
            EventHandler handler = AddRootEvent;
            if (handler != null)
            {
                handler(this, null);
            }
        }

        public string this[Guid key]
        {
            get
            {
                return Root[key];
            }
            set
            {
                Root[key] = value;
            }
        }
}

抽象方法public abstract int Add(int a, int b);的IL:

.method public hidebysig newslot abstract virtual 
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
} // end of method Code_06_03::Add

編譯器把Add方法當作一個虛方法,在子類中可以被重寫。

虛方法protected virtual string GetAddress(string addressID)的IL:

.method family hidebysig newslot virtual 
        instance string  GetAddress(string addressID) cil managed
{
//省略
}

它本來就是一個虛方法,所以編譯器並沒有特殊對待它。

方法public void AddRoot(Guid id, string rootName)的IL:

.method public hidebysig instance void  AddRoot(valuetype [mscorlib]System.Guid id,
                                                string rootName) cil managed
{
//省略
}

也是一個普通的對象方法。

接口

1) 可以定義屬性及索引器,但不能定義字段。

2) 可以定義事件。

3) 可以定義方法,僅僅是方法簽名的約定,不得有實現,在實現類中對該方法進行具體實現,有點類似於抽象類的抽象方法。

4) 不可以定義虛方法。

5) 不可以定義任何靜態成員。

6) 接口成員默認是全開放的,不得有訪問修飾符。

如下,定義一個接口:

    public interface ICode_06_01
    {
        string Name { get; set; }
        int Add(int a, int b);
        event EventHandler AddEvent;
    }

方法int Add(int a, int b);的IL:

.method public hidebysig newslot abstract virtual 
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
} // end of method ICode_06_01::Add

可以看到,定義的時候,我們並沒有為其指定可訪問修飾符(編譯器也不允許我們明文指定其可訪問修飾符),但編譯器默認將它的訪問級別指定為public。另外是把它當作一個抽象的虛方法。

至於成員屬性和事件,編譯器則將它們當作普通的對象屬性和對象事件對待,會為它們生成相應的get/set和add/remove 方法,並無特別之處。

第三節 實現方式的區別

抽象類的實現

由於抽象類也是類,所以對它的實現就像普通的繼承一樣,子類通過繼承可以得到抽象類的公有成員,且可以重寫部分成員,如虛方法和抽象方法等。如下是對Code_06_03類的實現:

public class Code_06_04 : Code_06_03
    {
        public override int Add(int a, int b)
        {
            return a + b;
        }
        protected override string GetAddress(string addressID)
        {
            return "BeiJing";
        }
        string _addressPrefix = "China ";
        public override string Address
        {
            get { return _addressPrefix; }
        }
    }

來看一下編譯器的工作:

可以看到類Code_06_04是標准、明白、徹底地對類Code_06_03的繼承。兩個重寫的方法Add和GetAddress都是普通的對象方法,只是依然被當作虛方法來看待,來看一下Add方法的IL:

.method public hidebysig virtual instance int32 
        Add(int32 a,
            int32 b) cil managed
{
//省略
}

方法GetAddress的IL:

.method family hidebysig virtual instance string 
        GetAddress(string addressID) cil managed
{
//省略
}

因為這兩個方法保持着虛方法的特性,所以對於Code_06_04類的子類,同樣還可以重寫這兩個方法。

屬性成員Address這里還是一普通的對象屬性。

接口的實現

對接口的實現跟對抽象類的實現相似,如下是對接口ICode_06_01的實現類:

View Code
public class Code_06_02 : ICode_06_01
    {
        string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        public int Add(int a, int b)
        {
            OnAdded();
            return a + b;
        }

        public event EventHandler AddEvent;
        void OnAdded()
        {
            EventHandler handler = AddEvent;
            if (handler != null)
            {
                handler(this, null);
            }
        }
    }

來看一下編譯器的工作:

它與普通類的區別不大,只是很明確的是實現了接口ICode_06_01,來看一下它的IL:

.class public auto ansi beforefieldinit ConsoleApp.Example06.Code_06_02
       extends [mscorlib]System.Object
       implements ConsoleApp.Example06.ICode_06_01
{
} // end of class ConsoleApp.Example06.Code_06_02

可以看到,類Code_06_02不僅繼承於System.Object類,同時還實現了接口ICode_06_01。再來看一下對於接口中的方法,編譯器是如何處理的。Add.IL:

.method public hidebysig newslot virtual final 
        instance int32  Add(int32 a,
                            int32 b) cil managed
{
//省略
}

編譯器認為Add方法具有虛方法的特性。而對於屬性和事件,依然是普通的實現,如get/set、add/remove。

另外,接口還支持顯示實現接口,我們上面討論的Code_06_02類對接口的實現默認是隱式實現。

在接口的實現類內部,可以存在一個與接口某一方法名(包括簽名)完全相同的方法,但要求那個對接口實現的方法必須是顯示實現,如下代碼:

        public int Add(int a, int b)
        {
            return a + b;
        }

        int ICode_06_01.Add(int a, int b)
        {
            OnAdded();
            return a + b;
        }

可以看出顯示實現就是在方法前加上接口名的前綴和點號(ICode_06_01.),同時也可以看到,顯示實現接口的方法是不能有可訪問修飾符的,編譯器會對其進行private處理。那如何才能調用顯示實現的接口方法呢?可以將實現類的對象轉為一個接口變量,再調用該變量的相應方法。如下代碼:

            Code_06_02 code0602 = new Code_06_02();
            ICode_06_01 icode0602 = code0602;
            icode0602.Add(1, 2);

而對於抽象類的實現,是不能進行顯示實現的!

 

第四節 應用中的區別

1) 抽象類保留一普通類的部分特性,定義可能已經實現的方法行為,方法內可以對數據成員(如屬性)進行操作,且方法可以相互溝通。而接口僅僅是定義方法的簽名,就像規則,只是約定,並沒有實現。

2) 抽象類的派生類可以原封不動地得到抽象類的部分成員,接口的實現類如果想要得到接口的數據成員,則必須對其進行重寫。

3) 一個類只能繼承於一個抽象(類),但可以實現多個接口,並且可以在繼承一個基類的基礎上,同時實現多個接口。

4) 抽象類和接口都不能對其使用密封sealed,事實上這兩者都是為了被其他類繼承和實現,對其使用sealed是沒有任何意義的。

5) 抽象類可以對接口進行實現。

6) 抽象類更多的用於“復制對象副本”,就是我們常說的“子類與父類有着is a的關系”,它更多關注於一個對象的整體特性。接口更多傾向於一系列的方法操作,這些操作在當前上下文中既有着相同作用對象,又相互隔離。

7) 某些時候,抽象類可以與接口互換

下面我們通過生活中見的紅娘拉線的示例,來說明抽象類與接口給我們變成帶來的方便性,下面代碼既可以使用抽象類也可以使用接口。

一般(這里說明的是一般性況下)只要想通過紅娘接線找到自己另一半的,在紅娘(Matchmaker)安排甲去見已以前,都會安排他/她(wooer)應該怎么跟對方溝通,加深對方對自己的好感印象,這些話是紅娘提供的,但說的動作,還是求婚者通過自己的“說話”本能來表達,只是紅娘在教求婚者說哪些套話而已。

一般我們會如下定義:

View Code
/// <summary>
    /// 紅娘
    /// </summary>
    public class Matchmaker
    {
        string message;

        /// <summary>
        /// 指導求婚人如何表達
        /// </summary>
        public void Teach()
        {
            message = "從見你的第一眼起,我就認為你會是那個每天早上跟我一起下床的人。但這絕對不是一見鍾情,那是什么?我會在我們老去的那一天告訴你。";
            Wooer wooer = new Wooer();
            wooer.Say(message);
        }
    }
    /// <summary>
    /// 求婚人
    /// </summary>
    public class Wooer
    {
        /// <summary>
        /// 向對方表達
        /// </summary>
        /// <param name="message"></param>
        public void Say(string message)
        {
            Console.WriteLine(message);
        }
}

客戶程序:

    public class ClientApp
    {

        public void Work()
        {
            Matchmaker matchmaker = new Matchmaker();
            matchmaker.Teach();
        }
    }

我們通常都會這么寫,簡單明了,實現功能沒問題。

紅娘在想:老娘一個人怎么能應付得了你們這一群剩男剩女!個個都有特殊情況,我哪里真有分身術!干脆我搭建一個平台,你們按我的要求填表,按我的步驟走就行了

然而這里的紅娘Matchmaker是強依賴Wooer。因為畢竟無論求婚人是男是女,不僅要口頭語言表達,還有自己獨特的肢體語言表達,比如男人會哼哼嗨嗨地表達,女人會哼哼唧唧地表達,Wooer要變了,那這個時候Matchmaker就得變。這個要求就是把所有求婚人抽象出來,下面我們引入接口,將Wooer抽象出來為一個接口IWooer,讓Matchmaker依賴於IWooer,而不直接依賴於具體求婚人。接下來如下改造代碼:

View Code
/// <summary>
    /// 紅娘
    /// </summary>
    public class Matchmaker
    {
        string message;
        /// <summary>
        /// 指導求婚人如何表達
        /// </summary>
        public void Teach(IWooer wooer)
        {
            message = "從見你的第一眼起,我就認為你會是那個每天早上跟我一起下床的人。但這絕對不是一見鍾情,那是什么?我會在我們老去的那一天告訴你。";
            wooer.Say(message);
        }
    }
    /// <summary>
    /// 求婚人
    /// </summary>
    public interface IWooer
    {
        /// <summary>
        /// 求婚人自己准備的語言,有自己的真家伙拿出來實戰!
        /// </summary>
        string Message { get; }
        /// <summary>
        /// 肢體表達
        /// </summary>
        void Action();
        /// <summary>
        /// 語言表達
        /// </summary>
        /// <param name="message"></param>
        void Say(string message);
    }

    public class ManWooer : IWooer
    {
        public string Message
        {
            get { return "我擁有制造印超機的技術"; }
        }
        public void Action()
        {
            //抱着對方、動手動腳、眼神像彎尺
            //這些動作無法用代碼表達,你懂的,如餓狼一樣。。。
        }
        public void Say(string message)
        {
            //先動起來預熱
            this.Action();
            //接着再表達
            Console.WriteLine(message + this.Message);
        }
    }
    public class WomanWooer : IWooer
    {
        public string Message
        {
            get { return "我擁有1+2個點"; }
        }
        public void Action()
        {
            //側身向着對方、朦朧的眼神含情默默、相見恨晚,總是想看一眼對方,但害羞的臉蛋紅紅
            //這些動作無法用代碼表達,你懂的,如羔羊一樣。。。
        }
        public void Say(string message)
        {
            //先動起來預熱
            this.Action();
            //接着再表達
            Console.WriteLine(message + this.Message);
        }
    }
再來看看客戶程序:
    public class ClientApp
    {

        public void Work()
        {
            Matchmaker matchmaker = new Matchmaker();

            //matchmaker.Teach();

            IWooer wooer;
            //求婚者是男人
            wooer = new ManWooer();
            matchmaker.Teach(wooer);

            //求婚者是女人
            wooer = new ManWooer();
            matchmaker.Teach(wooer);
            matchmaker.Teach(wooer);
        }
    }

很明顯,現在Matchmaker已經不再直接依賴Wooer了,只依賴第三方IWooer,不論來的是男人還是女人,只要你按照紅娘要求的步驟就行了,如此開放的平台,說不定明年就能創業板闖關成功!

 

小 結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM