Agile Software Development –LSP Liskov substitution principle

Source: Internet
Author: User

Liskov substitution principle: subtypes (subtype) must be able to replace their base type (BaseType).

Violation of LSP situation

Breaches of LSP often result in the use of run-time type checking in a way that is clearly a violation of the OCP. Typically, an explicit if statement or If/else chain is used to determine the type of an object, so that the correct behavior for that type can be selected.

structPoint {Doublex, y;} Public enumshapetype {square, circle}; Public classshape{PrivateShapeType type;  PublicShape (ShapeType t) {type =T;}  Public Static voidDrawshape (Shape s) {if(S.type = =shapetype.square) (s asSquare).        Draw (); Else if(S.type = =shapetype.circle) (s asCircle).    Draw (); }} Public classcircle:shape{PrivatePoint Center; Private Doubleradius;  PublicCircle ():Base(shapetype.circle) {} Public voidDraw () {/*Draws the Circle*/}} Public classsquare:shape{PrivatePoint topleft; Private Doubleside;  PublicSquare ():Base(shapetype.square) {} Public voidDraw () {/*draws the square*/}}

Obviously, the Drawshape function violates the OCP. It must know each of the possible derived classes of the shape class, and you must change it each time you create a new class that derives from the shape class.

The Shape class and circle class cannot replace the shape class in fact it violates the LSP. This violation also forces the Drawshape function to violate the OCP. As a result, breaches of LSP may also potentially violate the OCP.

More subtle violations of the situation

 Public classrectangle{PrivatePoint topleft; Private Doublewidth; Private Doubleheight;  Public DoubleWidth {Get{returnwidth;} Set{width =value;} }     Public DoubleHeight {Get{returnheight;} Set{height =value;} }}

Let's say that this application works well and is installed in many places. As with any successful software, the user's needs change from time to times. One day, the user is not satisfied with the function of just manipulating the rectangle, requiring the addition of an action square.

We often say that inheritance is a is-a (is a) relationship. That is, if a new type of object is considered to satisfy a is-a relationship with an object that already has a class, then the class of the new object should derive from the class of the existing object.

In a general sense, a square is a rectangle. It is therefore logical to treat the square class as derived from the rectangle class. However, this idea brings some subtle but several issues that merit attention. In general, these problems are difficult to meet until we write the code to find out.

The square class does not require both height and width. But Square will still inherit them from the rectangle. Obviously it's a waste. Let's say we don't really care about memory efficiency. Deriving square from Rectangle also produces some other problems, and square inherits the set method properties of width and height. These properties are inappropriate for square because the squares are equal in length and width. This is an important sign that there is a problem. However, this problem can be avoided. We can rewrite width and height in the following ways:

 Public classsquare:rectangle{ Public New DoubleWidth {Set        {            Base. Width =value; Base. Height =value; }    }     Public New DoubleHeight {Set        {            Base. Height =value; Base. Width =value; }    }}

But consider the following function:

void F (Rectangle R) {    R.width=+;   call Rectangle.setwidth}

If we pass a reference to the square object to this function, the square object will be destroyed because its length does not change. This clearly violates the LSP. When an object of a derived class of rectangle is passed in as a parameter, function f does not run correctly. The reason for the error is that setwidth and setheight are not declared as virtual in rectangle, so they are not polymorphic.

Self-compatible rectangle classes and square classes

 Public classrectangle{PrivatePoint topleft; Private Doublewidth; Private Doubleheight;  Public Virtual DoubleWidth {Get{returnwidth;} Set{width =value;} }     Public Virtual DoubleHeight {Get{returnheight;} Set{height =value;} }} Public classsquare:rectangle{ Public Override DoubleWidth {Set        {            Base. Width =value; Base. Height =value; }    }     Public Override DoubleHeight {Set        {            Base. Height =value; Base. Width =value; }    }}

The real problem

Now square and rectangle all seem to work. It seems that the design is self-compatible and correct. However, this conclusion is wrong. A self-compatible design is not necessarily compatible with all user programs. Consider the following function:

void g (Rectangle r) {    5;     4 ;     if  - )    {        thrownew Exception ("badarea! " );    }}

This function thinks that the passed in must be rectangle, and calls its members width and height. For rectangle, this function works correctly, but throws an exception if the square object is passed in. So the real question is: the writer of function G assumes that changing the width of the rectangle does not cause its long change.

It is clear that changing the width of a rectangle does not affect its long hypothesis that it is reasonable! However, not all objects that can be used as rectangle are satisfied with this hypothesis. If you pass an instance of a square class to a function like g that makes that assumption, the function will behave incorrectly. The function g is fragile for square/rectangle hierarchies.

Function g shows that there are functions that use rectangle objects that do not correctly manipulate the square object. For these functions, square cannot replace rectangle, so the relationship between square and rectangle is contrary to the LSP principle.

Some people argue about the problems that exist in function g, arguing that the writer of function G cannot assume that the width and length are independent. The authors of G will not unify this claim. function g takes rectangle as a parameter. And indeed there are some invariant properties and the principle that the description is clearly applicable to the rectangle class, one of the invariant properties is the length and width are independent. The creator of G can assert this invariance entirely. By the time the writer of Square violated the invariance.

What is really interesting is that square writers do not violate the invariance of squares. Because the square is derived from rectangle, the square writer violates the invariance of rectangle!

Validity is not an essential attribute

The LSP allows us to draw a very important conclusion: a model, if isolated, does not have a true sense of validity. the validity of the model can only be represented by its client program.

   When considering whether a particular design is appropriate, it is not possible to see this solution completely in isolation. It must be examined according to the reasonable assumptions made by the user of the design (these reasonable assumptions often appear in the form of assertions in unit tests written for base classes.) This is a good reason to practice test-driven development.

Does anyone know what reasonable assumptions the user of the design will make? Most of these assumptions are difficult to predict. In fact, if the view is going to predict all of these assumptions, the system we get is likely to be filled with the stench of unnecessary complexity. Thus, as with all other principles, it is usually best to predict only those most obvious breaches of the LSP and postpone all other predictions until the associated vulnerability stinks.

Based on contract design

Many developers may be uncomfortable with the concept of a "reasonable hypothesis" behavior. How do you know what the customer really wants? There is a technique that allows the assumptions in these rivers to be clarified, thus supporting the LSP. This technology is referred to as contract-based (design by CONTRACT,DBC)

With DBC, the writer of the class explicitly prescribes a contract for that class. The author of the customer code can use the contract to learn how the behavior can be relied upon. A contract is specified by declaring a precondition (precondition) and a post condition (postcondition) for each method. In order for a method to execute, the precondition must be true. After execution, the method is to ensure that the post condition is true.

When an object is used through an interface of a base class, the user only knows the base class's preconditions and post conditions. Therefore, derived class objects cannot expect these users to follow a stronger precondition than the base class. In other words, they must accept everything that the base class can accept. At the same time, the derived class must be consistent with all the post conditions of the base class. In other words, their behavior and output cannot violate any of the constraints that the base class has already identified. The user of the base class should not be disturbed by the output of the derived class.

Specifying a contract in a unit test

You can specify a contract by writing unit tests. Unit tests make the behavior of the class clearer by thoroughly testing the behavior of a class. The writer of the client code goes back to the unit tests so that they can know what reasonable assumptions should be made about the classes to be used.

Replace inheritance with the method of extracting public parts

Extracting the public part is an effective tool. If there are some common attributes in the two subclasses, then it is likely that other classes will later be required.

  If a set of classes supports a common responsibility, then they should inherit the accusation from a common superclass.

If the public superclass does not yet exist, create one and put the public duties into it. After all, the usefulness of such a class is certain-you have shown that some classes will inherit these responsibilities. However, a later extension to the system might add a new subclass that is likely to support the same responsibilities in new ways. At this point, the newly created superclass might be an abstract class.

The OCP is at the heart of many of Ood's claims. If this principle is applied effectively, the application will be more maintainable, reusable, and robust. LSP is one of the main principles that makes OCP known as possible. The substitution of a formal subtype allows a module that uses a base type to be extensible without modification. This kind of substitution must be implicit in the developer's ability to rely on. Thus, if you do not have a contract that supports base types in your code, you must have a good, broad understanding of the contracts.

The correct definition of a subtype is replaceable, where the substitution can be defined by a display or an implicit contract.

Excerpt from: [Mei]robertc.martin, Micahmartin, Aaron, Sun Yu translation Agile Software Development principles, patterns and practices (C # revision) [M], People's post and Telecommunications publishing house, 2013, 135-151,

Agile Software Development –LSP Liskov substitution principle

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.