里氏替换原则来源:
我们都知道面向对象有三大特性:封装、继承、多态。所以我们在实际开发过程中,子类在继承父类后,根据多态的特性,可能是图一时方便,经常任意重写父类的方法,那么这种方式会大大增加代码出问题的几率。比如下面场景:类C实现了某项功能F1。现在需要对功能F1作修改扩展,将功能F1扩展为F,其中F由原有的功能F1和新功能F2组成。新功能F由类C的子类C1来完成,则子类C1在完成功能F的同时,有可能会导致类C的原功能F1发生故障。这时就有人提出了里氏替换原则。里氏替换原则这项原则最早是在1988年,由麻省理工学院一位姓里的女士(Liskov)提出来的。
里氏替换原则定义:
严格的定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
也就是说,在面向对象设计中,如果用一个基类对象替换一个子类对象,程序不会发生错误,但是软件实体使用用一个子类对象,但是改实体不一定能使用该子类的基类对象,如果使用,程序有可能就会出问题。
例如有两个类,一个类为CLassA,另一个是CLassB类,并且CLassB类是CLassA类的子类,那么一个方法如果可以接受一个CLassA类型的基类对象base的话,如:MethodA(base),那么它必然可以接受一个CLassA类型的子类对象sub,MethodA(sub)能够正常运行。反过来的代换不成立,如一个方法MethodB接受CLassA类型的子类对象sub为参数:MethodB(sub),那么一般而言不可以有MethodB(base),除非是重载方法。
例子:沪深A股板块数据请求的里氏替换简析:
在本实例中,可以考虑增加一个新的抽象类StockBlock,而将ShangA和ShenA类作为其子类,数据请求类Quote类针对抽象客户类StockBlock编程,根据里氏代换原则,能够接受基类对象的地方必然能够接受子类对象,因此将Quote中的Request()方法的参数类型改为Code,如果需要增加新类型的客户,只需将其作为String类的子类即可。重构后的结构如图2所示:
里氏替换使用总结:
里氏替换包括以下四层含义:
(1).子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
(2).子类中可以增加自己特有的方法。
(3).当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
(4).当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。