里氏代换原则
在面向对象的程序设计中,里氏替换原则(Liskov Substitution principle)是对子类型的特别定义。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。【例】正方形不是长方形。在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。优点(1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。(2)子类可以增加自己特有的方法。(3)当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。(4)当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。
如何理解里氏替换原则
里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象
里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
在使用里氏代换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。
里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)是面向对象设计中的五大原则之一,它是由计算机科学家芭芭拉·利斯科夫提出的。该原则规定,所有引用父类对象的地方,都可以顺利地使用其子类的对象代替,而不会出现程序错误或异常。换言之,若一个类的方法使用父类作为参数,那么它的子类也应该能够被传递到该方法中,而且不应该对该方法的行为产生任何影响。此外,子类还应该可以在不改变父类方法的前提下,扩展或重写父类的方法。这个原则的主要目的是确保类的扩展性和灵活性,同时降低代码的耦合度。如果不遵守LSP,就会导致代码臃肿、冗长,难以维护和扩展。 LSP有三个重要的条件:1.子类必须完全实现父类的方法。这意味着,子类不能删除任何父类的方法,也不能改变它们的行为。2.子类可以有自己的方法。子类可以拥有自己的方法,但不能改变父类的方法。3.子类可以有自己的属性。 子类可以拥有自己的属性,但它们必须与父类相同或更具体。 举个例子,如果我们有一个Animal类和一个Dog类,Dog类是Animal类的子类。Animal类有一个makeSound()方法,而Dog类扩展了该方法,它可以不仅可以发出动物的声音,还可以摇尾巴。在这种情况下,Dog类遵循了LSP。另一个例子是Shape类和Rectangle类。Shape类是一个抽象类,定义了一个计算面积的方法。Rectangle类继承自Shape类,并实现了计算矩形面积的方法。在这种情况下,Rectangle类仍然遵循了LSP,因为它扩展了Shape类的功能,没有修改原有的行为。总之,LSP需要我们在设计类时注重代码的可维护性、可扩展性和可重用性,避免重复代码和不必要的代码耦合。只有遵循LSP原则,才能更好地实现面向对象设计的目标,提高代码质量和开发效率。