抽象基类与接口,共性与个性的选择

本文详细比较了C#中的抽象类与接口的特点及适用场景,解释了两者的相似之处与不同之处,并通过示例阐述了如何选择合适的继承机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C#中的抽象基类(Abstract Class)和接口(Interface)都实现了面向对象中的继承机制,它们通过定义抽象方法匀实现了对继承类的功能上的约束,那么在如何在恰当的场景选择恰当的继承机制是我们在设计中经常会遇到的问题,我们先来对比两者之间的区别与联系。  相同点  都可以被继承。  本身都不能被实例化  不同点  抽象类是一个不完整的类,需要进一步细化;而接口只是一个行为上的规范。  抽象基类不能实现多继承,而接口可以实现多继承。  抽象基类可以定义字段、属性、包含有实现的方法。接口可以定义字段、索引器、事件但不能定义字段和包含实现的方法。      共性、个性与选择  有的书上写到C#中推荐使用接口(Interface)来替代抽象基类(Abstract Class),并强调使用接口的诸多好处,这点我不敢苟同,从上面列表中看来,两者之间还是存在不少差异的,而这种差异的存在性必然决定了适用场景的不同,例如在抽象基类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,提高代码的可重用性,这是抽象类的优势所在;而接口中只能包含抽象方法。至于何时使用抽象基类何时使用接口关键还是取决于用户是如何看待继承类之间的联系的,用户更加关心的是它们之间的个性差异还是它们之间的共性联系。举个生活中的例子加以说明。  如果给你三个对象分别是人、鱼、青蛙,让你为他们设计个基类来概括它们之间的联系,那么首先给你的感觉肯定是它们个体间的差异性较大,很难抽象出共性,然而若让你概括他们行为之间的共性,你可能想了想会意识到他们都会游泳,只不过是游泳方式迥异。那么这时你就应当考虑使用接口而不是抽象基类,原因有三条:  个性大于共性。  差异较大的个性间具有某些相同的行为。  相同行为的实现方式有较大区别。  设计为: 好,这时再给你三个对象,分别是鲫鱼、鲤鱼、金鱼,仍然让你设计基类来概括它们之间的联系,那么你第一个意识到的肯定是它们都属于鱼类,其次是他们游泳的方式可能稍有差异,这时就应当使用抽象基类而不是接口,对比着上面的例子,原因也有三条:  1       interface  ISwim 2        { 3           void  Swim(); 4        } 5   6       public   class  Person : ISwim 7        { 8           public   void  Swim() 9            { 10               // Swimming in person's style. 11            } 12        } 13   14       public   class  Frog : ISwim 15        { 16           public   void  Swim() 17            { 18               // Swimming in frog's style. 19            } 20        } 21   22       public   class  Fish : ISwim 23        { 24           public   void  Swim() 25            { 26               // Swimming in fish's style. 27            } 28        } 共性大于个性  共性相同的个体间必然具有相同的属性与行为。  相同行为的实现方式具有一定区别。  设计为: 总结:  1       abstract   public   class  Fish 2        { 3           abstract   public   void  Swim(); 4        } 5   6       public   class  鲫鱼 : Fish 7        { 8           public   override   void  Swim() 9            { 10               // Swim like a 鲫鱼 11            } 12        } 13   14       public   class  鲤鱼 : Fish 15        { 16           public   override   void  Swim() 17            { 18               // Swim like a 鲤鱼 19            } 20        } 21   22       public   class  金鱼 : Fish 23        { 24           public   override   void  Swim() 25            { 26               // Swim like a 金鱼 27            } 28        } 观察在使用接口或是使用抽象基类的几条理由中,第三条理由其实是一样的,它所描述的是面向对象中多态的概念,即通过覆盖父类的方法来实现,在运行时根据传递的对象引用,来调用相应的方法。第二条理由开始产生分歧,接口更加强调了继承对象间具有相同的行为,而抽象类同时还强调了继承对象间具有相同的属性。而真正将接口与抽象基类区分开的则是理由一,归纳如下:  当在差异较大的对象间寻求功能上的共性时,使用接口。  当在共性较多的对象间寻求功能上的差异时,使用抽象基类。  抽象类和接口区别总结   1 .类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类     接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中     2 .接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;      3 .一个类一次可以实现若干个接口,但是只能扩展一个父类      4 .接口可以用于支持回调,而继承并不具备这个特点.      5 .抽象类不能被密封。     6 .抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的。   7 .(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。     8 .抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。     9 .好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。     10 .尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.     11 .如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法      如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。       如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。 --------------------------------------------------------------------- 抽象类:   a.包含一个或多个抽象方法或属性的类必须定义为抽象类    b.继承抽象类的派生类必须实现抽象类的所有抽象方法或属性,否則該派生类也必是抽象类,至到完全实现为止.    c.抽象类必须是公共的( public ).    d.抽象类不存在对象实例,但可以申明引用变量.另外抽象类中可以有非抽象方法,供派生类调用.  接 口:    1 .接口中定义抽象的方法,属性,索引,事件     2 .接口必须由类來实现,故其只能是默认public的,不可顯式申明,不能申明为  static      3 .实现接口的类必须完全实现接口中的所有的方法属性,索引,事件,不能只实现一部分     4 .一个类只能继承一个基类,但可以实现多个接口,其中继承的基类必须放在最前面,然后是接口,它們用逗號分開.  ----------------------------- 何时用抽象类,何时用接口? ※ 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。 ※ 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。 ※ 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。 ※ 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。 --------------------------------- 1 、接口中只能有方法,不能有字段;抽象类中即可以有方法,也可以有属性和字段;  2 、接口中的方法不需要实现,而抽象类中可以有实现了的方法;  3 、接口在一定程度上可以代替多继承。  4 、实现接口,也就意味着必须实现接口中的所有方法。如果继承抽象类的子类还是抽象类,则可以只实现部分方法。  5 、接口和抽象类都不被实例化成对象。 ----------------------------------- 首先要明确抽象类和接口使用上的区别,类只能单一的继承,如果一个类的方法只让其派生类实现,那么就可以用抽象类,如果方法是被多个类使用,那么就使用接口。  例如:   使用抽象类:    abstract   class  A   {     abstract   public   void  put();   }    class  B : A   {     public   void  put()    {      Console.WriteLine( " B " );    }   }    // -----------------------------------------   使用接口:    interface  Ia   {     public   void  put();   }      class  A : Ia   {     public   void  put()    {      Console.WriteLine( " A " );    }   }      class  B : Ia   {     public   void  put()    {      Console.WriteLine( " B " );    }    }

posted on 2007-08-06 17:31 寸芒 阅读(66) 评论(3)  编辑 收藏

#2楼 [楼主]   回复  引用  查看    

abstract class A  {   abstract public void put();  }  class B : A  {   public void put()   {     Console.WriteLine("B");   }  } 应该改为: abstract class A  {   abstract public void put();  }  class B : A  {   public override void put()   {     Console.WriteLine("B");   }  } ------------------  使用接口:  interface Ia  {   public void put();  } 这里也错了 不能有public 等修饰符
#1楼  [ 楼主]    回复   引用   查看     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值