里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。其严格表述以下:若是对每个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的全部程序P在全部的对象o1代换o2时,程序P的行为没有变化,那么类型S是类型T的子类型。这个定义比较拗口且难以理解,所以咱们通常使用它的另外一个通俗版定义:编程
里氏代换原则(Liskov Substitution Principle, LSP):全部引用基类(父类)的地方必须能透明地使用其子类的对象。spa |
里氏代换原则告诉咱们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,若是一个软件实体使用的是一个子类对象的话,那么它不必定可以使用基类对象。例如:我喜欢动物,那我必定喜欢狗,由于狗是动物的子类;可是我喜欢狗,不能据此判定我喜欢动物,由于我并不喜欢老鼠,虽然它也是动物。设计
例若有两个类,一个类为BaseClass,另外一个是SubClass类,而且SubClass类是BaseClass类的子类,那么一个方法若是能够接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然能够接受一个BaseClass类型的子类对象sub,method1(sub)可以正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么通常而言不能够有method2(base),除非是重载方法。对象
里氏代换原则是实现开闭原则的重要方式之一,因为使用基类对象的地方均可以使用子类对象,所以在程序中尽可能使用基类类型来对对象进行定义,而在运行时再肯定其子类类型,用子类对象来替换父类对象。继承
在使用里氏代换原则时须要注意以下几个问题:接口
(1)子类的全部方法必须在父类中声明,或子类必须实现父类中声明的全部方法。根据里氏代换原则,为了保证系统的扩展性,在程序中一般使用父类来进行定义,若是一个方法只存在子类中,在父类中不提供相应的声明,则没法在以父类定义的对象中使用该方法。ip
(2) 咱们在运用里氏代换原则时,尽可能把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实如今父类中声明的方法,运行时,子类实例替换父类实例,咱们能够很方便地扩展系统的功能,同时无须修改原有子类的代码,增长新的功能能够经过增长一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。ci
(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。开发
在Sunny软件公司开发的CRM系统中,客户(Customer)能够分为VIP客户(VIPCustomer)和普通客户(CommonCustomer)两类,系统须要提供一个发送Email的功能,原始设计方案如图1所示:编译器 图1原始结构图 在对系统进行进一步分析后发现,不管是普通客户仍是VIP客户,发送邮件的过程都是相同的,也就是说两个send()方法中的代码重复,并且在本系统中还将增长新类型的客户。为了让系统具备更好的扩展性,同时减小代码重复,使用里氏代换原则对其进行重构。 |
在本实例中,能够考虑增长一个新的抽象客户类Customer,而将CommonCustomer和VIPCustomer类做为其子类,邮件发送类EmailSender类针对抽象客户类Customer编程,根据里氏代换原则,可以接受基类对象的地方必然可以接受子类对象,所以将EmailSender中的send()方法的参数类型改成Customer,若是须要增长新类型的客户,只需将其做为Customer类的子类便可。重构后的结构如图2所示:
图2 重构后的结构图
里氏代换原则是实现开闭原则的重要方式之一。在本实例中,在传递参数时使用基类对象,除此之外,在定义成员变量、定义局部变量、肯定方法返回类型时均可使用里氏代换原则。针对基类编程,在程序运行时再肯定具体子类。