Design Mode ----- Richter substitution principle

Richter substitution principle

Open Closed Principle (Open Closed Principle) is the basis for building maintainability and reusability of code. It emphasizes good design code can not be extended by modifying the new functionality is achieved by adding new code, without the need to change existing code that may work. Abstraction (Abstraction) and polymorphism (Polymorphism) is the main mechanism to achieve this principle, and inheritance (Inheritance) is abstract and polymorphic main methods.

So what rules designed to ensure the use of the inheritance of it? Which features excellent level design has inherited it? What lured us in building a hierarchical structure does not comply with the open closed principle of it? These are the questions this article will answer.

Richter Substitution Principle (LSP: The Liskov Substitution Principle)

Function uses the base object pointers or references to the object must be able to use the derived class at a derived classes do not understand.

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

Barbara Liskov in 1988 raised this principle:

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

A simple example of the principles contrary LSP

A very clear example of the violation of the principle is to use the LSP RTTI (Run Time Type Identification) function performed is selected according to the object type.

void DrawShape(const Shape& s)
{
    if (typeid(s) == typeid(Square))
        DrawSquare(static_cast<Square&>(s)); 
    else if (typeid(s) == typeid(Circle))
        DrawCircle(static_cast<Circle&>(s));
}    

Obviously there is a lot of questions designed DrawShape function. It must know the fertility sub-class of all Shape base class, and when there is a new sub-class is created it must modify this function. In fact, many people see the function structure are considered to be cursed object-oriented design.

Square and rectangular, contrary to the principles subtleties

In many cases contrary to the principles of the LSP are very subtle way. Contemplates the use of a class Rectangle application, is described as follows:

public class Rectangle
{
    private double _width;
    private double _height;

    public void SetWidth(double w) { _width = w; }
    public void SetHeight(double w) { _height = w; }
    public double GetWidth() { return _width; }
    public double GetHeight() { return _height; }
}

Imagine this application can work well, and has been deployed to multiple locations. Like all successful software, its users raised new demands. Suppose a user requires a day in addition to the application process than rectangular (the Rectangle) also can handle square (Square).

Generally speaking, it is the inheritance of the relationship is-a. In other words, if a new object with a prior target to meet is-a relationship, then the new class of object should be from the class of an existing object inherited.

Obviously a rectangle is a square, and meet all customary purposes use. This therefore establishes a relationship of is-a, Square logical model may be derived from Rectangle.

Use of the is-a relationship is one of the basic techniques of object-oriented analysis (Object Oriented Analysis) of. A is a square (is-a) rectangular, should all classes derived from Rectangle Square class. However, this way of thinking will cause some subtle very serious problem. Before we usually do not actually use these codes, these problems can not be foreseen.

On this issue, our first clue may be the Square class does not need _height and _width member variables, although in any case it will inherit them. We can see that this is a waste, but if we continue to create hundreds of Square objects, this waste will behave very obvious.

Nevertheless, we can assume that we are not very concerned about the memory overhead. That there is a problem? of course! Square class will inherit SetWidth and SetHeight method. These methods Square is completely inappropriate, because a square of the same length and width. This should be another significant clue. However, there is a way to circumvent this problem. We can override SetWidth and SetHeight method. As follows:

public class Square : Rectangle
{
    public void SetWidth(double w)
    {
        base.SetWidth(w);
        base.SetHeight(w);
    }
    public void SetHeight(double w)
    {
    base.SetWidth(w);
    base.SetHeight(w);
    }
}

Now, regardless of who set the Square object Width, Height It will be followed by changes accordingly. When set Height, Width will also change. After doing so, Square looks perfect. Square remains a subject seems very reasonable mathematics square.

public void TestCase1()
{
    Square s = new Square();
    s.SetWidth(1); // Fortunately sets the height to 1 too.
    s.SetHeight(2); // sets width and heigt to 2, good thing.
}

But now look at the following methods:

void f(Rectangle r)
{
    r.SetWidth(32); // calls Rectangle::SetWidth
}

References to this approach if we pass a Square object, the Square object will be corrupted because it will not change the Height. Here clearly contrary to the principle of LSP, this function does not work under the conditions of the parameters derived object. And because there is no reason for the failure to set SetWidth SetHeight and virtual functions of the parent class Rectangle.

We can easily solve this problem. But despite this, when you create a derived class will lead to make changes to the parent class, it usually means that the design is flawed, specifically, is it contrary to the principles of OCP. We might think that the real design flaw is forgotten and the SetWidth SetHeight to virtual function, and we've fixed this problem. But, in fact, it is difficult to justify, because the Rectangle Height and Width is no longer an atomic operation. Whatever the reason we set them to virtual, we would not expect the presence Square.

Also, assume that we received this argument, and solve these problems. We finally got the following code:

public class Rectangle
{
    private double _width;
    private double _height;

    public virtual void SetWidth(double w) { _width = w; }
    public virtual void SetHeight(double w) { _height = w; }
    public double GetWidth() { return _width; }
    public double GetHeight() { return _height; }
}

public class Square : Rectangle
{
    public override void SetWidth(double w)
    {
      base.SetWidth(w);
      base.SetHeight(w);
    }
    public override void SetHeight(double w)
    {
      base.SetWidth(w);
      base.SetHeight(w);
    }
}

Root of the problem

At this moment we have two classes, Square and Rectangle, and it seems to work. No matter what you do to Square, it can still be consistent with the definition of mathematics in the square. And no matter what you do to a Rectangle object, it will also meet the definition of mathematics rectangle. And when you pass a function Square Rectangle object to a pointer may be received or cited, can guarantee the consistency Square square.

In this case, we may conclude that this model was now a self-consistent (self-consistent) and correct. However, this conclusion is wrong. All users are not necessarily a self-consistent model of it are consistent!

(Note: The self-consistency that is logical and self-consistent concepts, ideas, etc. before and after the first refers to the consistency between the construction of a scientific theory of a number of basic assumptions, underlying assumptions and derived from these basic assumptions of a series of logical conclusion between, between the various conclusions must be compatible, not conflicting logical self-consistency is also required to build all logical reasoning and mathematical calculations are correct theory of the process of logical self-consistency is a theory able to set up the necessary condition.)

Consider the following method:

void g(Rectangle r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    Assert.AreEqual(r.GetWidth() * r.GetHeight(), 20);
}

This function calls SetWidth SetHeight and methods, and that these functions belong to the same Rectangle. Rectangle This function can work, but if you pass a parameter into the Square assertion error occurs.

So this is the real problem: whether to write this function programmer can assume that a change of Rectangle Height Width will not change the value of?

Obviously, the programmer to write the function g made a very reasonable assumption. Square and passed to a function that pose such a problem. Thus, those who have received Rectangle object pointer exists or function references are likewise not subject to the normal operation Square. These functions reveals the violation of the principle of the LSP. Further, Rectangle Square derived from these functions are also destroyed, so it is contrary to the principles of OCP.

Effectiveness than internal

This leads to a very important conclusion. From an isolated point of view, a model can not be meaningfully authenticate itself. The validity of the model can only be expressed by its users. For example, in isolation Square and Rectangle, we find that they are self-consistent and effective. But when we make a programmer's point of reasonable assumptions from one base class to look at them, this model was broken.

Therefore, when considering a particular design is reasonable, must not simply from the point of view to look at it in isolation, but must be analyzed from the reasonable assumption that the design of the user's point of view .

In the end what is wrong?

So in the end what happened? Why look very reasonable Square and Rectangle bad model of it? Could it be that a Square is a Rectangle is wrong with you? Is-a relationship does not exist?

Do not! A square can be a rectangle, but a Square object is definitely not a Rectangle object. why? Because behavior and the behavior of a Square object a Rectangle object is inconsistent. From the perspective of behavioral point of view, a Square is not a Rectangle! The real concern is that software design behavior (behavior).

LSP principle in our understanding of OOD is-a relationship is related to the behavior. Private behavior is not intrinsic, but extrinsic public behavior, is dependent on user behavior. For example, the above function g dependence of a basic fact that changes between each other Rectangle Width and Height are no dependencies. And this relationship is not dependent on the kind of external public behavior, and you may also have other programmers think.

In order to still comply with the principle of LSP, OCP and also in line with the principles, all derived classes must meet the expected behavior of the user base class .

Design by Contract (Design by Contract)

Bertrand Meyer in 1988 describes the relationship between the LSP principles and design by contract. Design using contract, a class method to declare pre-conditions and post-conditions. Pre-condition is true, then the method can be executed. But before the method call is completed, the process itself will also be set up to ensure that post-conditions.

We can see the Rectangle method of SetWidth post-conditions are:

1 Contract.Ensures((_width == w) && (_height == Contract.OldValue<double>(_height)));

Set preconditions and post-conditions derived class rules, Meyer describes:

…when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.

In other words, when the base class interface used by the object, the client only knows the class of the base class preconditions and post-conditions. Thus, derived class objects can not be stronger than the expected customer category subject to pre-conditions in the base class. In other words, they must accept any base class acceptable condition. Furthermore, the derived class must meet the postconditions defined in the base class. In other words, their behavior and output can not violate any limit has been established with the base class. Client class of the base class must not have any doubts about the output derived class.

Obviously, postcondition Square :: SetWidth (double w) weaker than Rectangle :: SetWidth (double w), because it does not meet the conditions of the base class clause "(_height == Contract.OldValue (_Height)). "Therefore, Square :: SetWidth (double w) contrary to the established contract given base class.

Some programming languages, there is direct support for pre- and post-conditions. You can directly define these conditions, and then verify that the system is running. If the programming language does not directly support defined conditions, we can also consider manually define these conditions.

to sum up

Open Closed Principle (Open Closed Principle) is the core of many object-oriented design inspiration ideas. Application in line with this principle in terms of maintainability, reusability and robustness will perform better. Richter substitution principle is an important way to achieve OCP principles. Only when the derived class can be completely replace their base class, the base class of functions used to be able to be safely reused, then the derived class can also be assured modified.

Reference material

Guess you like

Origin www.cnblogs.com/MessiXiaoMo3334/p/11822956.html