10th Chapter Lsp:liskov Substitution principle
Liskov substitution principle: subtypes (subtype) must be able to replace their base types (base type).
10.1 Violations of LSP
10.1.1 Simple Example
Violations of the LSP resulted in a violation of the OCP:
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*/}}
It is clear that the Drawshape function violates the OCP. It must know every possible derived class of the shape class and must change it each time a new class derived from the shape class is created.
10.1.2 more subtle violations of the situation
Here is a rectangle type:
PublicClassrectangle{PrivatePoint TopLeft;private double width; Span style= "color: #0000ff;" >private double height; public double Width { Span style= "color: #0000ff;" >get {return width;} set {width = value;}} public double Height { Span style= "color: #0000ff;" >get {return height;} set {height = value;}}
One day, users ask for the ability to add squares.
We often say that inheritance is a is-a (is a) relationship. 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. Write the following self-compatible rectangle class and square class code:
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! " ); }
For rectangle, this function works correctly, but throws an exception if the square object is passed in. All, the real question is: The writer of function G assumes that changing rectangle often does not result in wide changes.
Obviously, changing the width of a rectangle does not affect his long yes hypothesis is reasonable! However, not all objects passed as rectangle satisfy this hypothesis. The function g is fragile for square and rectangle hierarchies. For G, Square cannot replace rectangle, so the relationship between square and rectangle is a violation of the LSP.
Validity is not an essential attribute
A model, if seen in isolation, does not have a true sense of validity. The validity of the model can only be represented by its client program. So, as with other principles, just predict the most obvious breaches of the LSP and postpone all other predictions until the associated vulnerability stinks.
ISA is about behavior.
The is-a relationship in Ood is in the way of behavior, and the behavior mode can be reasonably assumed, which is the client program depends on.
10.2 Replacing inheritance with methods of extracting public parts
See the following code:
Public classline{PrivatePoint p1; PrivatePoint p2; PublicLine (Point P1, point p2) { This. P1 = P1; This. P2 =P2;} PublicPoint P1 {Get{returnP1;} } PublicPoint P2 {Get{returnP2;} } Public DoubleSlope {Get{/*Code*/} } Public Doubleyintercept {Get{/*Code*/} } Public Virtual BOOLIsOn (point P) {/*Code*/}} Public classlinesegment:line{ PublicLineSegment (Point P1, point P2):Base(P1, p2) {} Public DoubleLength () {Get{/*Code*/} } Public Override BOOLIsOn (point P) {/*Code*/}}
At first glance, they will feel that there is a natural inheritance between them. However, these two classes still violate the LSP in subtle ways.
Line users can expect all points that have a linear linear correspondence with the. For example, the point returned by the Yintercept property is the intersection of the line and the axis. Since this point has a linear correspondence with the line, the user of the lines can expect ISON (yintercept ()) ==true. However, for many instances of LineSegment, this statement will fail.
A simple solution solves the problem of line and LineSegment, which also illustrates an important tool for Ood. If we can have access to both the line class and the LineSegment class, we can propose an abstract base class for the public part of these two classes. As follows:
Public Abstract classlinearobject{PrivatePoint p1; PrivatePoint p2; PublicLinearobject (Point p1, point p2) { This. P1 = P1; This. P2 =P2;} PublicPoint P1 {Get{returnP1;} } PublicPoint P2 {Get{returnP2;} } Public DoubleSlope {Get{/*Code*/} } Public Doubleyintercept {Get{/*Code*/} } Public Virtual BOOLIsOn (point P) {/*Code*/}} Public classline:linearobject{ PublicLine (Point P1, point P2):Base(P1, p2) {} Public Override BOOLIsOn (point P) {/*Code*/}} Public classlinesegment:linearobject{ PublicLineSegment (Point P1, point P2):Base(P1, p2) {} Public DoubleGetLength () {/*Code*/} Public Override BOOLIsOn (point P) {/*Code*/}}
Extracting the public part is an effective tool. If there are some common features in the two classes, then it is likely that other classes will later appear to have those attributes. For example, the Ray class:
Public class ray:linearobject{ publicbase(P1, p2) {/*code*/} Public Override BOOL IsOn (point P) {/*code*/}}
10.3 Heuristic rules and idioms
Derived classes that perform less than the base class typically cannot replace their classes, and therefore violate the LSP.
See the following code:
Public class base{ publicvirtualvoid f () {/*some code* / }}publicclass derived:base{ publicoverride void f () {}}
The function f is implemented in base. However, in derived, the function f is degenerate. Perhaps, derived programmers think that function f is useless in derived. Unfortunately, base users do not know that they should not call F, so there is a substitution violation.
The existence of degenerate functions in degenerate classes does not always mean that the LSP is violated, but it is worth noting when this is the case.
10.4 Conclusion
The OCP is at the heart of many of Ood's claims. LSP is one of the main reasons for making OCP possible.
The meaning of the term is-a is too broad to be defined as a subtype. The correct definition of a subtype is replaceable.
Excerpt from: "Agile Software Development: principles, patterns and Practices (C # Edition)" Robert C.martin Micah Martin
Reprint please specify the source:
Jesselzj
Source: http://jesselzj.cnblogs.com
Agile Software Development: principles, patterns and practices--the 10th chapter Lsp:liskov substitution principle