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();  
      }      
    }  
  }  
   *************************

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值