Six Principles of Design Patterns (2): The Liskov Substitution Principle

There must be quite a few people who were just as confused about the name of this principle when I first saw it. In fact, the reason is that this principle was first proposed in 1988 by a woman named Barbara Liskov at the Massachusetts Institute of Technology.

Definition 1: If for every object o1 of type T1, there is an object o2 of type T2, so that all programs P defined by T1 have no change in the behavior of program P when all objects o1 are replaced by o2 , then type T2 is a subtype of type T1.

Definition 2: All references to a base class must transparently use objects of its subclasses.

Origin of the problem: There is a function P1, which is completed by class A. Now the function P1 needs to be expanded, and the expanded function is P, wherein P is composed of the original function P1 and the new function P2. The new function P is completed by the subclass B of the class A, and the subclass B may cause the original function P1 to malfunction while completing the new function P2.

Solution: When using inheritance, follow the Liskov Substitution Principle. When class B inherits class A, in addition to adding new methods to complete the new function P2, try not to override the method of parent class A, and try not to overload the method of parent class A.

         Inheritance includes such a meaning: all the methods that have been implemented in the parent class (as opposed to abstract methods) are actually setting a series of specifications and contracts, although it does not mandate that all subclasses must comply with these contract, but if subclasses arbitrarily modify these non-abstract methods, it will cause damage to the entire inheritance system. The Liskov Substitution Principle expresses this meaning.

        As one of the three major characteristics of object-oriented, inheritance brings great convenience to program design, but also brings disadvantages. For example, the use of inheritance will bring intrusiveness to the program, reduce the portability of the program, and increase the coupling between objects. If a class is inherited by other classes, when this class needs to be modified, all subclasses must be considered. class, and after the parent class is modified, all functions involving the subclass may fail.

        To illustrate the risk of inheritance, we need to complete a function of subtracting two numbers, which is the responsibility of class A.

[java]  view plain
  1. class A{  
  2.     publicint func1(int a, int b){   
  3.         return a-b;  
  4.     }  
  5. }  
  6.   
  7. public class  Client{   
  8.     publicstaticvoid main(String[] args){    
  9.         A a = new A();  
  10.         System.out.println("100-50="+a.func1(10050));  
  11.         System.out.println("100-80="+a.func1(10080));  
  12.     }  
  13. }  


 operation result:

100-50=50
100-80=20

        Later, we need to add a new function: complete the addition of two numbers, and then sum with 100, which is responsible for class B. That is, class B needs to complete two functions:

  • Subtract two numbers.
  • Add the two numbers, then add 100.

        由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:

[java]  view plain
  1. class B extends A{  
  2.     public int func1(int a, int b){  
  3.         return a+b;  
  4.     }  
  5.       
  6.     public int func2(int a, int b){  
  7.         return func1(a,b)+100;  
  8.     }  
  9. }  
  10.   
  11. public class Client{  
  12.     public static void main(String[] args){  
  13.         B b = new B();  
  14.         System.out.println("100-50="+b.func1(10050));  
  15.         System.out.println("100-80="+b.func1(10080));  
  16.         System.out.println("100+20+100="+b.func2(10020));  
  17.     }  
  18. }  

类B完成后,运行结果:

100-50=150
100-80=180
100+20+100=220

        我们发现原本运行正常的相减功能发生了错误。原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

        里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • When the method of the subclass implements the abstract method of the superclass, the postconditions of the method (that is, the return value of the method) are stricter than those of the superclass.

        It seems incredible, because we will find that we often violate the Liskov Substitution Principle in our own programming, and the program still runs fine. So everyone will have this question, what will be the consequences if I have to follow the Liskov substitution principle?

        The consequence is that the chances of the code you write going wrong are greatly increased.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326581716&siteId=291194637