小天:我看来看去,练来练去,总觉得接口和抽象类区别不大。
老田:事实上差别还是蛮大的,仅仅从使用方面来说。接口主要是用来定义两个程序通信的契约;而抽象类则是用来封装对象间公用的行为。二者在设计起初的目标完全不同,可惜在实际应用中被太多的人误解。因为涉及到多个程序之间的通信,接口就是规定了这个程序那些东西必须要公开。
小天:为什么要公开?
老田:因为程序模块之间需要配合。而不是做出一个个完全独立的程序。那么程序之间的衔接通过什么呢?当然是接口。所以从角度上来说接口和抽象类最大的不同在于用处的不同。接口是对外,而抽象类则是对内规划程序的方向。我们可以理解为接口主外,而抽象类主内。
咱们还是先单独分析:
抽象类是一种特殊的类,它无法实例化。所以,我们会问,那我们为什么还需要一个无法实例化的类呢?一个抽象类仅用来被继承的,即它只允许其他类继承它,但自己不能实例化。对继承的子集,抽象类的优势是增强了某些层次结构。简而言之,它是一种规范,使所有继承它的子类都带有一样的层次结构或标准。
接口不是一个类。一个接口没有执行机制。它只能有一个对象间交互的契约,换句话说,它只能够定义方法名字,方法怎么实现的一无所有。有一点是和抽象类相似的,它也具有规范的能力,接口被用来定义所有子类的结构,或者说是定义一套子类方法。它们间的主要区别就是,一个类能实现或执行多个接口,而只能从一个抽象类继承。在C#中,不支持向C++那样一个类可以多重继承,但接口的运用,从另外的角度看,可以用来实现了多继承。
对比:
当我们创建一个接口,相当于我们基本上创建了一套没有执行的方法,需要我们在子类中过载(Overriden)实现。这样做有一个非常显著的优势,它提供了一种方法,使一个类可以成为两个类的一部分。当我们创建一个抽象类,相当于我们创建了一个基类,它拥有一个或着多个完整的方法,但至少有一个方法没有实现,即被声明为抽象方法。若所有的方法都没实现,那它和接口的功能是一样的,只是它还遵守无法被继承的规则。抽象类的目的是提供了一个基本类,定义了子类将如何设计,允许程序员在子类中实现这些方法。
下表分别列出他们的差异:
特点 |
接 口 |
抽象类 |
成员 |
不能包含实现区块 |
可以包含实现区块 |
多继承 |
一个类能从几个接口继承 |
一个类只能继承一个抽象类 |
访问修饰符 |
不可以包含非public成员 |
可以包含非public成员 |
实例化 |
不能被实例化 |
不能被实例化 |
继承 |
能继承其他接口 |
能继承其他的类,包含非抽象类 |
控制版本 |
无法控制版本 |
可以控制版本 |
回调 |
支持回调 |
不能实现回调,因为继承不支持 |
成员 |
方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法; |
可以定义字段、属性、包含有实现的方法。 |
值类型and引用类型 |
可以作用于值类型和引用类型。例如,Struct就可以继承接口,而不能继承类。 |
只能作用于引用类型 |
实现方式 |
一个接口没有任何实现的代码只显示方法名 |
抽象类可以提供完整的方法实现的代码 |
核心与外围 |
接口用来定义类的外围功能. |
抽象类多用来定义一个类的核心层 |
使用场合 |
如果很多实现都共享一些方法,则用接口比较 |
如果很多实现使用同一系列方法,使用一样的属性,则用抽象类较好 |
速度 |
要求更多的时间,去找到实际的子类实现的方法 |
比较快 |
功能扩展性 |
如果我们要给接口添加一个方法,我们要捕捉所有使用该接口的实现子类,并分别添加一个新的方法,并实现它. |
如果我们要给抽象类添加一个新的方法我们可以在抽象实现,也可以在子类实现 |
使用规则与场合
请记住,面向对象思想的一个最重要的原则就是:面向接口编程。
1. 借助接口和抽象类,23个设计模式中的很多思想被巧妙的实现了,我认为其精髓简单说来就是:面向抽象编程。
2. 抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
3. 接口着重于CAN-DO关系类型,而抽象类则偏重于IS-A式的关系;
4. 接口多定义对象的行为;抽象类多定义对象的属性;
5. 接口定义可以使用public、protected、internal 和private修饰符,但是几乎所有的接口都定义为public,原因就不必多说了。
6. “接口不变”,是应该考虑的重要因素。所以,在由接口增加扩展时,应该增加新的接口,而不能更改现有接口。
7. 尽量将接口设计成功能单一的功能块,以.NET Framework为例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一个公共方法。
8. 接口名称前面的大写字母“I”是一个约定,正如字段名以下划线开头一样,请坚持这些原则。
9. 在接口中,所有的方法都默认为public。
10. 如果预计会出现版本问题,可以创建“抽象类”。例如,创建了狗(Dog)、鸡(Chicken)和鸭(Duck),那么应该考虑抽象出动物(Animal)来应对以后可能出现风马牛的事情。而向接口中添加新成员则会强制要求修改所有派生类,并重新编译,所以版本式的问题最好以抽象类来实现。
11. 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。
12. 对抽象类不能使用new关键字,也不能被密封,原因是抽象类不能被实例化。
13. 在抽象方法声明中不能使用 static 或 virtual 修饰符。
MSDN推荐的使用场合
14. 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。
15. 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。
16. 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。
17. 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。
老田关于学习的提示
18. 如果你绝对是一个零基础的初学者,你暂时不用考虑将这两者混合起来使用,你只需要将每一种学懂就行。否则的话,你会觉得仅仅是这两个知识点就足以放弃学习了。所以我建议,这两个知识点,一定要等你以后有大量经验了,准备学习设计模式的时候再来认真复习,现在只要知道接口和抽象类是咋用的就行了。
好了,本章到这里也就结束了,看目录很长,其实我是故意的,因为这章的内容就是整个C#面向对象编程的核心的中心,重点中的重中之重。
本文章为天轰穿原创作品,转载请注明出处及作者。