这几天园子里有几篇随笔对抽象类与接口进行了比较和分析,具体的概念Anytao在[你必须知道的.NET] 第二回:对抽象编程:接口和抽象类中已经总结的很好了,大家可以参考。每个人对面向对象都有自己的理解,我也来谈谈我的认识。
为什么CLR不允许多重继承?
这个问题乍看起来也许很小白,不允许就是不允许,哪来那么多为什么呢?你以为你是小沈阳啊?
呵呵,不好意思,我还没完,接下来的问题是,为什么所有的类都继承自System.Object?为什么不能实例化抽象(包括抽象类和接口)?为什么接口不能有实现?……对于这些问题,我们真的认真思考过吗?
其实,面向对象思想,是对现实世界最好的诠释。一切皆对象,在OO中如此,在现实生活中亦是如此。在虚拟的程序中,我们经常会对现实中的各种事物进行建模,于是“人”、“汽车”等事物就成了代码中的Person和Car。我们为什么会自然而然地进行这样的转换呢?这正是面向对象编程思想的魅力所在。“人”和“汽车”在现实中都代表一类事物,他们有相同的行为和属性,这就是面向对象中类的概念。
许多年前,当我开始接触面向对象的时候,我并不能理解书本上晦涩的概念。然而随着CS水平的日益提高,我也逐渐对面向对象有了自己的认识。AK47就是一个类,当你按下B+4+1的时候,就初始化了这样一个实例。它有自己的属性:大小、颜色、子弹数量、换弹时间;也有自己的行为:发射、换子弹。此后,再将面向对象与现实世界相联系,我逐渐理解了前面提到的那几个问题。他们的答案都是一个:因为现实世界就是这个样子的。
为什么所有的类都继承自System.Object?因为现实世界所有的事物都可以归为一个绝对抽象的基类。生物、非生物都是这个基类的派生。外形、运动等等就是这个基类的属性或行为。
为什么不能实例化抽象?因为现实世界中不存在抽象的具体事物。抽象是从众多的事物中抽取出共同的、本质性的特征,它并不是某一具体的事物。换句话说,如果存在可以具体化的抽象,那么这个抽象就不是真正意义上的抽象了,它对于事物共性的提取,是错误的。
为什么不允许多重继承?因为现实世界种不存在多重继承。派生类与基类的关系是IS-A,即派生类是一个基类。人就是动物,动物就是生物,不可能同时还是非生物。任何一个事物都有所属,我们把动物植物按照一定的纲、门(汗……)分类,就是这个原因。如果存在A既是B又是C,只能说明B是C或者C是B,如果B和C没有继承关系,那么你说A到底是B还是C呢?当然,随着科技的发展,新的事物层出不穷,有些无法无法按照单继承分类的事物,其实就是一个新的事物。比如,水路两用车,到底是属于车呢,还是属于船呢?其实都不是,它就是水路两用的交通工具,自成一派。
抽象类,还是接口?
抽象类和接口都属于以上提到的抽象,两者的在语法上的区别Anytao和其他园友已经总结的差不多了,不再赘述。我主要想和大家讨论一下在设计时如何选择抽象类和接口。
我的原则是,尽量按照现实世界的语义来判断。
众所周知,派生类与抽象类的关系是IS-A,实现类与接口的关系是CAN-DO。这就说明抽象类与子类之间的所属关系较接口来说更加明确。接口只是行为方式上的一种契约,一个类实现了一个接口,只能说明该类具有该接口所约定的行为,但它并不属于该接口(事实上,接口也无法成为其他类的所属者),因此我们把对抽象类的派生叫做继承,而把对接口的派生叫做实现。例如,IComparable接口为所有可进行比较的类提供一个抽象的约定,但也仅仅局限于该约定。在语义上,“可以比较的”并不是某一类事物全部行为和属性的抽象,因此并不能够成为其派生类的父类。
人和汽车肯定不属于一类事物,但是他们却可以有相同的行为,即Move。那么Person类和Car类虽然继承自不同的基类,但它们却可以实现同一个接口IMoveable(该接口仅包含一个Move方法)。
因此,在设计时,我们需要思考这个抽象是代表的一类事物,还是代表一类行为或属性。如果是一类事物,就设计为抽象类;如果是一类行为或属性,就设计为接口。如果发现某个子类不得不继承两个基类,那么就必须审视一下这两个基类的设计是否合理,是否应该提取一些接口出来,或者是否应该重新设计一个基类。
现在来回答为什么接口不能有实现的问题,因为现实世界……哎呀,哪里的砖头?……
呵呵,其实正如前面所说,接口只是一种规范的约定,它并不了解各个实现类的具体细节。抽象类Person可以对Move方法提供默认实现(走或跑),Car也同样可以(前进或倒退),但是IMoveable接口并不知道,能实现这个接口的类成千上万并且实现方式千奇百怪,要它提供一个默认的移动方式,实在强接口所难。
对于命名的建议
对于抽象类和接口的命名规范,我的建议是:将抽象类(类也是如此)命名为名词,将接口命名为I为前缀的形容词,最好以-able为后缀。
在.NET Framework里随处可见以-able为后缀的接口,如IComparable、IDisposable……意为“可……的”,这完全复合CAN-DO的语义定义。前面的IMoveable接口如果命名为IMove,在语义上或多或少地缺少了CAN-DO的意境。
当然,这只是一种建议。只要牢记接口是“可……的”这个原则,在选择抽象类和接口时就不会感到茫然了。
总之一句话,请参考现实世界。