面向对象设计原则之三:里氏替换原则
里氏替换原则(Liskov Substitution Principle LSP)
里氏替换原则是面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当子类可以替换基类,软件单位的功能不受影响时,基类才能真正的被复用,而子类也可以在基类的基础上增加新的行为。
Liskov提出了关于继承的原则:Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.----继承必须确保超类中所拥有的性质在子类中仍然成立。2002年,软件工程大师Robert C. Martin出版了一本《Agile Software DevelopmentPrinciples Patterns and Practices》,在文中他把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for their base types”也就是说子类必须能够替换成他们的基类。
里氏替换原则讲的是基类和子类的关系,只有这种关系存在的时候里氏替换原则才能成立。里氏替换原则是实现开放封闭原则的具体规范。这是因为:实现开放封闭原则的关键是抽象,而继承关系又是抽象的一种具体实现。
我们大家都打过CS的游戏,用枪射击杀人,如下类图:
枪的主要职责是射击,如何射击在各个具体的子类中定义。注意在类中调用其他类时务必调用父类或接口,如果不能掉话父类或接口,说明类的射击已经违反了LSP原则。
如果我们有一个玩具手 枪,该如何定义呢?我们先在类图2-1上增加一个类ToyGun,然后继承于AbstractGun类,修改后的类图如下:
玩具枪是不能用来射击的,杀不死人的,这个不应该写shoot方法,在这种情况下业务的调用类就会出现问题。为了解决这个问题,ToyGun可以脱离继承,建立一个独立的父类,为了做到代码可以服用,可以与AbstractGun建立关联委托关系,如下图:
因此,如果子类不能完整地实现父类的方法,那么建议断开父子继承关系,采用依赖,聚合,组合等关系代替继承。
子类可以有自己的属性或方法。
覆盖或实现父类的方法时输入的参数可以放大。
覆盖或实现父类的方法时输出结果可以被缩小。这是什么意思呢,父类的方法返回值是一个类型T,子类相同的方法(覆写)的返回值为类型S,那么根据里氏替换原则就要求S必须小于等于T,也就是说要么S和T是同一个类型,要么S是T的子类型。
采用里氏替换原则的目的就是增加程序的健壮性,需求变更时也可以保持良好的兼容性和稳定性,即使增加子类,原有的子类可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同业务逻辑。