2.3.3 Liskov替换原则
Barbara Liskov于1988年提出了著名的替换原则:"如果对于类型S的每个对象O1存在类型T的对象O2,那么对于所有定义了T的程序P来说,当用O1替换 O2并且S是T的子类型时,P的行为不会改变。"通俗地讲,就是子类型能够完全替换父类型,而不会让调用父类型的客户程序从行为上有任何改变。
Liskov替换原则 基于这样的一个事实,那就是客户程序在调用某一个类时,实际上是对该类的整个继承体系设定了一个契约,继承体系中的所有类必须遵循这一契约,即前置条件和 后置条件必须保持一致 。这就超越了继承中子类与父类之间形成的is-a关系,为对象继承加上了一把严格的枷锁。显然,Liskov替换原则对于约束继承的泛滥具有重要意义。
我个人认为,Liskov替换原则并不是要求子类不能新增父类没有的方法或者属性。因为从调用父类的客户程序的角度来说,它关心的仅仅是父类的行 为,只要子类对于父类的行为是可替换的,就不算是违背该原则。恰恰相反,当你发现父类拥有子类不希望继承,或者勉强继承会对子类造成破坏时 ,正可以说明这个继承体系可能存在问题,违背了Liskov替换原则。这就充分说明,子类并不关心父类的行为,但却需要遵循父类制定的规范或契约,以满足 客户调用父类的期望。正所谓"萧规曹随",如果前人制定的规范我们不遵循,反而要去打破,那就不是继承,而是铁了心要另起炉灶了。
一个经典的违反Liskov替换原则的例子是正方形与矩形之间的关系。这样的例子在谈对象设计的原则时,已经啰嗦得够多,这里我就不再赘述了 。这个例子带来的教训就是,现实世界中继承的例子,不能够完全直接套用在程序世界中。不过,作为设计的参照物,现实世界的很多规律与法则,我们仍然不可忽 视。例如鲸鱼和鱼,应该属于什么关系?从生物学的角度看,鲸鱼应该属于哺乳动物,而不是鱼类。没错,在程序世界中我们可以得出同样的结论。如果让鲸鱼类去 继承鱼类,就完全违背了Liskov替换原则。因为鱼作为父类,很多特性是鲸鱼所不具备的,例如通过腮呼吸,以及卵生繁殖。那么,二者是否具有共性呢? 有,那就是它们都可以在水中"游泳",从程序设计的角度来说,它们都共同实现了一个支持"游泳"行为的接口。
如图2-7所示的设计,可以看做是解决违背Liskov替换原则的一种常规方案,即提取两者之间的共同点,定义一个更为通用的接口,或者新的父类。
图2-7 鲸鱼和鱼的关系 |