Richter replacement principle of 23 design modes

Posted by blacklotus on Mon, 27 Sep 2021 13:27:58 +0200

Richter substitution principle

Thinking and explanation of inheritance in OO

1. Inheritance contains a layer of meaning: all implemented methods in the parent class are actually setting specifications and contracts. Although it does not force all subclasses to follow these contracts, if subclasses arbitrarily modify these implemented methods, it will damage the whole inheritance system.

2. Inheritance not only brings convenience to program design, but also brings disadvantages. For example, using inheritance will bring invasiveness to the program, reduce the portability of the program and increase the coupling between objects. If a class is inherited by other classes, all subclasses must be considered when the class needs to be modified, and all functions involving subclasses may fail after the parent class is modified.

3. Question: how to use inheritance correctly in programming? Richter substitution principle

Richter's replacement principle: it was proposed by a woman surnamed Li at MIT in 1988

If there is object o2 of type T2 for each object o1 of type T1, so that the behavior of program P does not change when all objects o1 are replaced with o2, then type T2 is a subtype of type T1. In other words, all references to the base class must be able to use the objects of its subclasses transparently.

When using inheritance, follow the Richter substitution principle and try not to override the methods of the parent class in the subclass

The Richter substitution principle tells us that inheritance actually enhances the coupling between the two classes. In appropriate cases, the problem can be solved through aggregation, composition and dependency.

The case of Richter's substitution principle

/**
 * Richter substitution principle
 *
 * @create: 2021/9/26
 * @author: Tony Stark
 */
public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11-3="+a.func1(11,3));
        System.out.println("1-8="+a.func1(1,8));
        B b = new B();
        System.out.println("11-3="+b.func1(11,3));
        System.out.println("1-8="+b.func1(1,8));
        System.out.println("11+3+9="+b.func2(11,3));

    }
}

/**
 *
 */
class A {
    /**
     * Returns the difference between two numbers
     *
     * @param num1
     * @param num2
     * @return
     */
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

/**
 * B Class inherits A and adds A new function to complete the addition of two numbers and then sum with 9
 */
class B extends A{
    @Override
    public int func1(int a, int b) {
        return a+b;
    }
    public int func2(int a,int b){
        return func1(a,b)+9;
    }
}

output

11-3=8
1-8=-7
11-3=14
1-8=9
11+3+9=23

Here, the original intention of our class B is to call methods to perform 11-3 operations, but because our class B rewrites the methods of class A, the result of our class 11-3 becomes 14

We found an error in the subtraction function that was normally running. The reason is that class B inadvertently rewrites the methods of the parent class, resulting in errors in the original functions

Common practice: the original parent and child classes inherit a more popular base class. The original inheritance relationship is removed and replaced by dependency, aggregation, composition and other relationships.

/**
 * Richter substitution principle
 *
 * @create: 2021/9/26
 * @author: Tony Stark
 */
public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));
        B b = new B();
        //Because class B no longer inherits class A, the caller will no longer think func1 is subtraction
        System.out.println("11+3=" + b.func1(11, 3));
        System.out.println("1+8=" + b.func1(1, 8));
        System.out.println("11+3+9=" + b.func2(11, 3));

        //Using the combination, you can still use the relevant methods of A
        
    }
}

/**
 * Create a more basic base class
 */
class Base {
    //Write more basic methods and members to the Base class

}

/**
 *
 */
class A extends Base {
    /**
     * Returns the difference between two numbers
     *
     * @param num1
     * @param num2
     * @return
     */
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

/**
 * B Class inherits A and adds A new function to complete the addition of two numbers and then sum with 9
 */
class B extends Base {
    /**
     *If B needs to use the method of A, it needs to use the combination relationship
     */
    private A a=new A();
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
    /**
     * I still want to use the class A method
     */
    public int func3(int a,int b){
        return this.a.func1(a,b);
    }
}

output

11-3=8
1-8=-7
11+3=14
1+8=9
11+3+9=23

What if we want to use the method of A at this time

System.out.println("11-3="+b.func3(11,3));
public class Liskov {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));
        B b = new B();
        //Because class B no longer inherits class A, the caller will no longer think func1 is subtraction
        System.out.println("11+3=" + b.func1(11, 3));
        System.out.println("1+8=" + b.func1(1, 8));
        System.out.println("11+3+9=" + b.func2(11, 3));

        //Using the combination, you can still use the relevant methods of A
        System.out.println("11-3="+b.func3(11,3));
    }
}

output

11-3=8
1-8=-7
11+3=14
1+8=9
11+3+9=23
11-3=8

The combination method can still use the method of A
This article learned from the video of Shang Silicon Valley teacher
here

Topics: Java Design Pattern unit testing