里氏代换原则(Liskov Substitution Principle, LSP)
1、里氏代换原则定义
若对于每一个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,用o1替换o2后,程序P的行为功能不变,则S是T的子类型。
What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
即,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类。而且它觉察不出基类对象和子类对象的区别。也就是说,在软件里面,把基类都替换成它的子类,程序的行为没有变化。反过来的代换不成立,如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。
2、里氏代换原则与“开-闭”原则的关系
实现“开-闭”原则的关键步骤是抽象化。基类与子类之间的继承关系就是抽象化的体现。因此里氏代换原则是对实现抽象化的具体步骤的规范。违反里氏代换原则意味着违反了“开-闭”原则,反之未必。
3、里氏代换原则的四层含义
1)子类必须完全实现父类的方法。在类中调用其他类是务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
2)子类可以有自己的个性。子类当然可以有自己的行为和外观了,也就是方法和属性
3)覆盖或实现父类的方法时输入参数可以被放大。即子类可以重载父类的方法,但输入参数应比父类方法中的大,这样在子类代替父类的时候,调用的仍然是父类的方法。即以子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松。
4)覆盖或实现父类的方法时输出结果可以被缩小。
4、里氏代换原则在设计模式中的体现
策略模式(Strategy)
如果有一组算法,那么就将算法封装起来,使得它们可以互换。客户端依赖于基类类型,而变量的真实类型则是具体策略类。这是具体策略焦色可以“即插即用”的关键。
合成模式(Composite)
合成模式通过使用树结构描述整体与部分的关系,从而可以将单纯元素与符合元素同等看待。由于单纯元素和符合元素都是抽象元素角色的子类,因此两者都可以替代抽象元素出现在任何地方。里氏代换原则是合成模式能够成立的基础。
代理模式(Proxy)
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。代理模式能够成立的关键,就在于代理模式与真实主题模式都是抽象主题角色的子类。客户端只知道抽象主题,而代理主题可以替代抽象主题出现在任何需要的地方,而将真实主题隐藏在幕后。里氏代换原则是代理模式能够成立的基础。
5、总结
里氏代换原则是对开闭原则的扩展,它表明我们在创建基类的新的子类时,不应该改变基类的行为。也就是不要消弱基类的行为。
面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。我经常说类的继承关系就是一种“Is-A”关系,实际上指的是行为上的“Is-A”关系,可以把它描述为“Act-As”。