2012年/08月/09日
继承是多态的前提
继承是现实系统中非常泛化的一种关系,在人类社会中也非常普遍,比如一个家族。面向对象如果来自对抽象数据类型的提升,它只关心数据结构的行为,如果它来自于为维根斯坦的哲学,它表示宇宙中的一个自管理的对象个体。
对象存在的前提是封装,封装了结构而开放了行为,开放的行为是对象的外在表现,如果我们希望对象的外在表现可以应场合而变,我们可以进一步把行为抽象,这样便得到接口。
如果系统全部用行为接口来交互,是不是声明式编程?
对象由它的类型构造,如果类型可以直接构造出对象,那么它是具体类型,如果类型不能直接构造对象,那么它是抽象类型,抽象类型是为了构造具体类型的,而不是为了构造具体对象的 在java语法上,把抽象类型转变为自动类型的过程就是继承,但是设计者并没有禁止从具体类型继承,但是最好的设计是继承的父亲都是抽象类型,最抽象的类型那就是接口。
就extends语法而言,还可以用于接口继承接口,这是完全的接口继承,但是类继承即继承接口又继承实现,在java中没有只继承实现的语法,C++是有的,而ruby这类语言根本就没有接口,所以只继承实现? 所以既然继承了接口,那就请保证接口的外在语义。里氏代换原则也是建立在这个基础之上。
接口无数据只有行为,如果有数据那是静态和final的,不需维护类的不变量,抽象类型可能有数据又有行为,行为必须保护好类的不变量,开放的数据,必须final,这个开放包括public和protected 最好情况是所有数据全部private,只能开放方法,共同维护了对象状态的一致性的方法需要final,如果不final请注明一致性维护规范,抽象方法是特意留给子类覆盖,这个方法具体化之后将和抽象类型中的其他方法一起构造出对象。
具体类型可能继承自一个抽象类型,或者不继承,也可能继承自一个具体类型,不继承则把握好封装的原则即可,如域必须私有,行为开放,小心被继承,如果被继承,请告知不变量维护规则 如果继承自抽象类型,请覆盖抽象方法即可,如果要覆盖具体方法,那么按照超类得覆盖规范维护好不变量。如果继承自具体类型,可以添加在自己的域,同时保护好自己的域不变量,调用超类行为来维护超类的不变量,如果要覆盖超类行为,请确保超类不变量维护规则。
继承中的保护类方法,可能是超类不开放给外界而只开放给子类使用的方法,请只使用而不要覆盖,如果这个保护方法是抽象的,那么它是不开放给外界,而是用来具体化类型的一个内部钩子,请实现它。 如果必须覆盖这个一个受保护的具体方法,那么这个方法应该是超类管理不变量的唯一方法,那么也可覆盖,所以超类在设计保护的方法时,要么作为抽象钩子,要么作为不变量唯一约束方法,要么用final来修饰表示只能使用。
总结起来:
- 接口只表达行为,也可以表达不变数据
- 抽象类型,表达部分不变量,但是行为不完整,需要子类来实现这些行为,他们专为子类设计
- 具体类型,可构造对象,但是也要提防被继承带来的问题
继承用语言机制来表达概念泛化,同时引入了多态,也方便的复用了代码,但是复用代码也可以用合成来实现,所以继承设计一定要从概念泛化,多态,代换原则等入手,记住单纯的is a关系并不一定构成继承关系。
原则:如果能用合成则尽量用合成,确实不能用合成的地方则仔细设计你的继承,多用final和private为上
继承又分为单继承和多继承,多继承在C++时代被批的一无是处,它导致了菱形问题,使代码组织变得复杂,降低了维护性 但是我们不可回避我们的现实空间,有很多是需要多继承来表达的,本应用多继承表达的模型如果选择单继承,最后得到的继承树是不优雅的,可能存在大量的重复,关于这个问题,大家可以看看extjs3到extjs4的重构。
在ruby,scala等语言中开始用用混入(mix in)的概念来支持多继承,但是被继承的类是有特殊限制的,这样就回避了在C++中出现的问题。