Designing the architecture of the source code of dating and making friends should follow the principle of Richter substitution

Keywords: architecture

The Liskov Substitution Principle (LSP) means that if there is an 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 defined by T1 are replaced with O2, then type T2 is a subtype of type T1.

This definition looks quite abstract. Let's re understand it. It can be understood as a dating source code. If it is applicable to a parent class, it must be applicable to its subclasses. All places that reference the parent class must be able to use the objects of its subclasses transparently. The subclass objects can replace the parent objects, and the logic of the dating source code remains unchanged. According to this understanding, the extended meaning is that a subclass can extend the functions of the parent class, but cannot change the original functions of the parent class.

(1) Dating source subclasses can implement the abstract methods of the parent class, but they cannot override the non abstract methods of the parent class.

(2) Dating source subclasses can add their own unique methods.

(3) When the method of the subclass overloads the method of the parent class, the preconditions of the method (i.e. the input / input parameters of the method) are more relaxed than the input parameters of the parent method.

(4) When the method of the subclass implements the method of the parent class (overriding / overloading or implementing abstract methods), the post condition of the method (i.e. the output / return value of the method) is more stringent than or the same as that of the parent class.

When talking about the opening and closing principle, I buried a foreshadowing. When obtaining the discount, I rewritten the getPrice() method covering the parent class and added a getOriginPrice() method to obtain the source code, which obviously violates the Richter substitution principle. Let's modify the code. We should not override the getPrice() method and add the getDiscountPrice() method:

public class JavaDiscountCourse extends JavaCourse {

    public JavaDiscountCourse(Integer id, String name, Double price) {

        super(id, name, price);

    }

    public Double getDiscountPrice(){

        return super.getPrice() * 0.61;

    }

}

Designing the source code structure of dating and making friends and using the Richter replacement principle has the following advantages:

(1) The proliferation of constraint inheritance is an embodiment of the opening and closing principle.

(2) Strengthen the robustness of the source code of dating and making friends. At the same time, it can also achieve very good compatibility when changing, improve the maintainability and scalability of the program, and reduce the risk introduced when changing the requirements.

Now let's describe a classic business scenario and explain the Richter substitution principle with the relationship between square, rectangle and quadrilateral. We all know that square is a special rectangle, so we can create a parent class Rectangle:

public class Rectangle {

    private long height;

    private long width;

    @Override

    public long getWidth() {

        return width;

    }

    @Override

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    public void setWidth(long width) {

        this.width = width;

    }

}

Create a Square class, and Square inherits the Rectangle class:

public class Square extends Rectangle {

    private long length;

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    @Override

    public long getWidth() {

        return getLength();

    }

    @Override

    public long getHeight() {

        return getLength();

    }

    @Override

    public void setHeight(long height) {

        setLength(height);

    }

    @Override

    public void setWidth(long width) {

        setLength(width);

    }

}

Create the resize() method in the test class. The width of the rectangle should be greater than or equal to the height. We let the height increase automatically until the height equals the width and becomes a square:

public static void resize(Rectangle rectangle){

    while (rectangle.getWidth() >= rectangle.getHeight()){

        rectangle.setHeight(rectangle.getHeight() + 1);

        System.out.println("width:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());

    }

    System.out.println("resize Method end" +

            "\nwidth:"+rectangle.getWidth() + ",height:"+rectangle.getHeight());

}

The test code is as follows:

public static void main(String[] args) {

    Rectangle rectangle = new Rectangle();

    rectangle.setWidth(20);

    rectangle.setHeight(10);

    resize(rectangle);

}

The operation results are shown in the figure below.

We find that the height is larger than the width, which is a very normal situation in a Rectangle. Now we replace the Rectangle class with its subclass Square, and modify the test code:

public static void main(String[] args) {

    Square square = new Square();

    square.setLength(10);

    resize(square);

}

The above code runs in an endless loop, which violates the Richter replacement principle. After replacing the parent class with the child class, the program running result does not meet the expectation. Therefore, there are some risks in our code design. Richter substitution principle only exists between parent and child classes, and constraint inheritance is rampant. Let's create an abstract quadrilateral interface based on rectangle and square:

public interface Quadrangle {

    long getWidth();

    long getHeight();

}

Modify the Rectangle class:

public class Rectangle implements Quadrangle {

    private long height;

    private long width;

    @Override

    public long getWidth() {

        return width;

    }

    public long getHeight() {

        return height;

    }

    public void setHeight(long height) {

        this.height = height;

    }

    public void setWidth(long width) {

        this.width = width;

    }

}

Modify Square class Square:

public class Square implements Quadrangle {

    private long length;

    public long getLength() {

        return length;

    }

    public void setLength(long length) {

        this.length = length;

    }

    @Override

    public long getWidth() {

        return length;

    }

    @Override

    public long getHeight() {

        return length;

    }

}

At this time, if we change the parameter of the resize() method to the quadrilateral interface Quadrangle, an error will be reported inside the method. Because the Square class Square has no setWidth() and setHeight() methods. Therefore, in order to restrict inheritance, the parameters of the resize() method can only use the Rectangle class.

Of course, in the architecture design of dating source code, we need to follow far more than this principle. Through the limitations of various principles, we can ensure the quality of the architecture design of dating source code and provide users with better experience. The above is the whole content of "designing the architecture of dating source code and the Richter replacement principle that should be followed". I hope it will be helpful to you.

Posted by bidnshop on Thu, 04 Nov 2021 08:48:05 -0700