C#中接口與抽象類的區別

.Net提供了接口,這個不同於Class或者Struct的類型定義。接口有些情況,看似和抽象類一樣,因此有些人認爲在.Net可以完全用接口來替換抽象類。其實不然,接口和抽象類各有長處和缺陷,因此往往在應用當中,兩者要結合來使用,從而互補長短。

 

接下來先說說抽象類和接口的區別。

區別一,兩者表達的概念不一樣。抽象類是一類事物的高度聚合,那麼對於繼承抽象類的子類來說,對於抽象類來說,屬於“是”的關係;而接口是定義行爲規範,因此對於實現接口的子類來說,相對於接口來說,是“行爲需要按照接口來完成”。這些聽起來有些虛,舉個例子。例如,狗是對於所有狗類動物的統稱,京哈是狗,牧羊犬是狗,那麼狗的一般特性,都會在京哈,牧羊犬中找到,那麼狗相對於京哈和牧羊犬來說,就屬於這類事物的抽象類型;而對於“叫”這個動作來說,狗可以叫,鳥也可以叫。很明顯,前者相當於所說的是抽象類,而後者指的就是接口。

 

區別二,抽象類在定義類型方法的時候,可以給出方法的實現部分,也可以不給出;而對於接口來說,其中所定義的方法都不能給出實現部分。

例如:

    public abstract class AbsTest

    {

        public virtual void Test()

        {

            Debug.WriteLine( "Test" );

        }

        public abstract void NewTest();

    }

 

    public interface ITest

    {

        void Test();

        void NewTest();

    }

 

區別三,繼承類對於兩者所涉及方法的實現是不同的。繼承類對於抽象類所定義的抽象方法,可以不用重寫,也就是說,可以延用抽象類的方法;而對於接口類所定義的方法或者屬性來說,在繼承類中必須要給出相應的方法和屬性實現。

 

區別四,在抽象類中,新增一個方法的話,繼承類中可以不用作任何處理;而對於接口來說,則需要修改繼承類,提供新定義的方法。

 

知道了兩者的區別,再來說說,接口相對於抽象類的優勢。

好處一,接口不光可以作用於引用類型,也可以作用於值類型。而抽象類來說,只能作用於引用類型。

 

好處二,.Net的類型繼承只能是單繼承的,也就是說一個類型只能繼承一個類型,而可以繼承多個接口。其實,我對於這一點也比較贊同,多繼承會使繼承樹變的混亂。

 

好處三,由於接口只是定義屬性和方法,而與真正實現的類型沒有太大的關係,因此接口可以被多個類型重用。相對於此,抽象類與繼承類的關係更緊密些。

 

好處四,通過接口,可以減少類型暴露的屬性和方法,從而便於保護類型對象。當一個實現接口的類型,可能包含其他方法或者屬性,但是方法返回的時候,可以返回接口對象,這樣調用端,只能通過接口提供的方法或者屬性,訪問對象的相關元素,這樣可以有效保護對象的其他元素。

 

好處五,減少值類型的拆箱操作。對於Struct定義的值類型數據,當存放集合當中,每當取出來,都需要進行拆箱操作,這時採用Struct+Interface結合的方法,從而降低拆箱操作。

如下給出兩者的簡單對比表格。

 

接口

抽象類

多繼承

支持

不支持

類型限制

沒有

有,只能是引用類型

方法實現

繼承類型中必須給出方法實現

繼承類中可以不給出

擴展性

比較麻煩

相對比較靈活

多層繼承

比較麻煩,需要藉助虛函數

比較靈活

 

打個管理方面類似的比喻:  

  你開個雜貨店,剛開始做生意時,由於買的東西較少,所以什麼物品都堆在一起,別人買什麼你就從中扒出來買。  

  後來你感覺這種方法非常不爽,每次扒來扒去太麻煩了,於是你把不同的產品放在不同的地方,如:廚房用品,洗衣用品等,更好的就用個貨架把他們分開;這樣若別人要個洗衣粉什麼的,就直接去洗衣用品去拿就OK了。  

  再後來就如現在的超市,分類越來越細,廚房用品->>鍋碗瓢盆,每個又都分別放在一個小貨架,相要什麼,直接去拿就OK了,省得想買個菜刀還滿超市跑。  

   

  接口也類似上面的管理概念;當然把所有的貨物全部椎放到你的門頭去買也可以,但在一定情況下,你不會竟爭過超市,當然你也可以在程序中把所有的功能都在類中實現,而不用接口,但在較大的項目中,扒來扒去找需要的功能類,自己都感覺煩!

接口,就像現在定的很多標準一樣;如果你的一個對象加了這個接口,我只要知道這個接口就可以調用你的東西了

**********************

我想從南京到上海,不同的人可能有不同的走法.我們就定義一個接口:  
  public   interface   NJtoSH  
  {  
  void   Go();  
  }  
   
 
張三可能坐汽車.  
 
李四可能坐飛機.  
  ...  
   
  public   class   ZS   :   NJtoSH  
  {  
  public   void   Go()  
  {  
  ByBus();  
  }  
  private   void   ByBus()  
  {  
  }  
  }  
   
  public   class   LS   :   NJtoSH  
  {  
  public   void   Go()  
  {  
  ByPlane();  
  }  
  private   void   ByPlane()  
  {  
  }  
  }

 

****************************

  簡單的說接口就是一個契約或者規範.比如遙控器,國家出臺了一個國家遙控器規範,明文要求所有的遙控器廠家都要遵循這個規範,如果不遵循規範就不給3C認證標誌,就不允許上市出賣..爲什麼要這個規範呢?大家在時間生活中會經常碰到,甲廠的遙控器不能遙控乙廠的電視,電視遙控器不能遙控其它電器如空調,冰箱.!原因是什麼呢?是各個遙控器都沒有遵循一個規範,電波有長有短,電壓有高有低,導致各自爲政,45!      
         
可以想像出國家遙控器標準只是是規定遙控器的一些重要技術指標,比如要發射波應該多長,電壓應該多高,...,但它絕對不會規範出遙控器的材質,形狀,重量和顏色,也是說規範把所有同遙控無關的東西都拋棄了!每個遙控器廠家只要遵循了規範,那麼對遙控器可以有任意的詮釋.比如A廠可以用鐵做,牢固無比,B廠可以用紙,可以任意摺疊,anyway,不管用什麼做,做出什麼樣子,只要遵循規範的遙控器就可以遙控所有的電器(當然電器廠家也要遵循一定的規範),甚至可以遙控導彈發射!利害吧,這就是接口的威力.      
         
再詳細點,接口就是一個規範,他和具體的實現無關!接口是規範(虛的),他只是一張紙,也是說在實際的使用中接口只有依託一個實現了它的類的實例,纔會有意義,如上面的各個廠家做的遙控器產品.每個實現接口的類(廠家)必需實現接口中所有的功能.     一旦一個類實現了一個接口,就可說一個類和接口捆綁了(這個很重要,做題目的時候會用到)  
         
來個例子  
          interface    
遙控器規範         //國家定義的遙控器規範     ,每個遙控器廠家必需實現(詮釋)  
          {  
                      int    
波長();  
                      int    
電壓();  
          }  
          class  
甲廠鐵遙控器   :   遙控器規範         //甲廠的遙控器實現(詮釋)了這個規範,它和遙控器規範捆綁了!,它可以在市場上出售了  
          {  
                      public     int    
波長();                                                 //規範上定義的指標      
                      public     int    
電壓();                                                 //規範上定義的指標      
                      public     int    
形狀()     {     正方形};                     //甲廠自己對該產品的詮釋          
                      public     int    
材質()     (         };                                     //甲廠自己對該產品的詮釋  
          }  
          class  
乙廠紙遙控器   :   遙控器規範         ////甲廠的遙控器實現(詮釋)了這個規範,它和遙控器規範捆綁了!,它可以在市場上出售了  
          {  
                      public     int    
波長();                                             ////規範上定義的指標      
                      public     int    
電壓();                                         //規範上定義的指標                  
                      public     int    
形狀()(     圓形);                     //甲廠自己對該產品的詮釋,是圓形  
                      public     int    
材質()(     );                             //甲廠自己對該產品的詮釋,用紙做,好酷!              
          }  
          class    
電器  
          {  
                  procedure    
接收遙控(遙控器規範             //電器上,接收遙控指令  
                  {.....  
                             
接收(遙控器規範.波長)     ;                              
                             
接收(遙控器規範.電壓);  
                              .....}              
            }          
          static     main()  
          {  
                 
甲廠鐵遙控器         ControlA         ;         //申明控制器對象  
                 
乙廠紙遙控器         ControlB         ;  
                  ControlA         =     new    
甲廠鐵遙控器();     //實例化控制器對象,這個時候系統在託管堆中爲該對象分配了空間  
                  ControlB         =     new    
乙廠紙遙控器()     ;  
                 
遙控器規範   ControlInterfaceA     =     (遙控器規範)遙控器1     ;         //把對象實例轉換成一個規範,爲什麼呢?因爲"我家的電器".只能識別遙控器規範,它識別不到具體的遙控器  
                 
遙控器規範   ControlInterfaceB     =     (遙控器規範)遙控器2;         //同上  
                 
電器         我家的電器     =     new     電器();  
                 
我家的電器.接收遙控(ControlInterfaceA)         //我用甲廠遙控器遙控我家的電器.     注意:     這裏的ControlInterfaceA是不能單獨存在的,它必要依賴實現了"遙控器規範"的類的實例"ControlA".道理很簡單,接口是一個指針,不會被分配空間,你就無法使用,只有和一個具體類的實例聯繫了,纔有了可以活躍空間.  
                 
我家的電器.接收遙控(ControlInterfaceB)         //我用乙廠遙控器遙控我家的電器  
                   
                  ...  
                  //
下面是我的的想像,我可以用遙控器來控制導彈發射!  
                 
我的導彈.接收遙控(ControlInterfaceA);  
                 
我的導彈.接收遙控(ControlInterfaceB);                  
                  ...  
          }  
  --------------------------------------------------------------------  
 
接口的執行  
 
好了,有了接口的概念,再來談c#程序在運行中是如何使用接口的,如何訪問接口函數.具體流程如下  
          a.
當調用一個接口的函數時,系統會去檢查這個接口對應實例是什麼?  
          b.
找到這個實例後,再去找這個實例對應的實例類是什麼(什麼是實例類,參看讀書筆記二)  
          c.
根據這個實例類去檢查該實例類是否和接口發生了捆綁(看是否實現了該接口,冒號後面就是)  
          d.
!如果實例類實現了該接口(發生了捆綁)     ,它就在這個實例類中找函數的定義.然後執行該函數.執行結束.  
          e.
如果沒找到,他就繼續往父類找,直到找到第一個和接口捆綁的父類爲止  
          f.
找到後,它再檢查該函數是否是虛擬函數,  
          g.
如果不是,他馬上就執行它     .  
          h    
如果是,麻煩了,系統又要從頭來過,去檢查該實例類的函數是否重載了該函數,...具體過程見(c#讀書筆記2).  
         
例子:  
           
  using   System;  
   
  namespace   ConsoleApplication1  
  {  
    ///   <summary>  
    ///   Class1  
的摘要說明。  
    ///   </summary>  
    public   interface     I  
    {  
      void     Func();  
    }  
                     
    class     A   :I      
    {  
      public     virtual     void     Func()      
      {      
        Console.WriteLine("FuncA";  
      }  
    }  
                   
    class     B     :     A     ,     I         //
注意這裏的意思?  
    {  
      public     void     Func()     {     Console.WriteLine("FuncB";}  
    }                  
                   
    class     C     :         A      
    {  
      public     override     void     Func()     {     Console.WriteLine("FuncC";}  
    }          
   
    class   Class1  
    {  
      ///   <summary>  
      ///  
應用程序的主入口點。  
      ///   </summary>  
      [STAThread]  
      static   void   Main()    
      {  
        I     a     =     new     A()     ;         //
申明瞭接口a,並馬上和一個類的實例發生關係了  
        I     b     =     new     B()     ;         //
申明瞭接口b,並馬上和一個類的實例發生關係了  
        I     c     =     new     C()     ;         //
申明瞭接口c,並馬上和一個類的實例發生關係了  
        a.Func()     ;                             //
檢查a的實例A,     發現A和接口I捆綁了,所以執行A的函數Func     ,結果:     FuncA  
        b.Func()     ;                             //
檢查b的實例B,     發現B和接口I捆綁了,所以執行B的函數Func     ,結果:     FuncB  
        c.Func()     ;                             //
家常c的實例C,發現其沒有和接口I捆綁,系統繼續找它的父類.     發現AI捆綁了,他就去找函數A,發現A是虛擬函數,系統又從頭來找類的實例C,發現C重載(override)Func,好了,馬上執行該函數.     結果是FuncC;  
        Console.ReadLine();  
      }      
    }  
  }  
   *************************

 

 

 

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