Agile Principles, Patterns, and Practices in C#
Another interesting and puzzling case of inheritance is the case of Line and LineSegment.[9] Consider Listings 10-7 and 10-8. At first, these two classes appear to be natural candidates for inheritance. LineSegment needs every member variable and every member function declared in Line. Moreover, LineSegment adds a new member function of its own, Length, and overrides the meaning of the IsOn function. Yet these two classes violate LSP in a subtle way. [9] Despite the similarity of this example to the Square/Rectangle example, it comes from a real application and was subject to the real problems discussed. Listing 10-7. Line.cs
Listing 10-8. LineSegment.cs
A user of Line has a right to expect that all points that are colinear with it are on it. For example, the point returned by the YIntercept property is the point at which the line intersects the Y-axis. Since this point is colinear with the line, users of Line have a right to expect that IsOn(YIntercept) == true. In many instances of LineSegment, however, this statement will fail. Why is this an important issue? Why not simply derive LineSegment from Line and live with the subtle problems? This is a judgment call. There are rare occasions when it is more expedient to accept a subtle flaw in polymorphic behavior than to attempt to manipulate the design into complete LSP compliance. Accepting compromise instead of pursuing perfection is an engineering trade-off. A good engineer learns when compromise is more profitable than perfection. However, conformance to LSP should not be surrendered lightly. The guarantee that a subclass will always work where its base classes are used is a powerful way to manage complexity. Once it is forsaken, we must consider each subclass individually. In the case of the Line and LineSegment, a simple solution illustrates an important tool of OOD. If we have access to both the Line and LineSegment classes, we can factor the common elements of both into an abstract base class. Listings 10-9, 10-10, and 10-11 show the factoring of Line and LineSegment into the base class LinearObject. Listing 10-9. LinearObject.cs
Listing 10-10. Line.cs
Listing 10-11. LineSegment.cs
Representing both Line and LineSegment, LinearObject provides most of the functionality and data members for both subclasses, with the exception of the IsOn method, which is abstract. Users of LinearObject are not allowed to assume that they understand the extent of the object they are using. Thus, they can accept either a Line or a LineSegment with no problem. Moreover, users of Line will never have to deal with a LineSegment. Factoring is a powerful tool. If qualities can be factored out of two subclasses, there is the distinct possibility that other classes will show up later that need those qualities, too. Of factoring, Rebecca Wirfs-Brock, Brian Wilkerson, and Lauren Wiener say: We can state that if a set of classes all support a common responsibility, they should inherit that responsibility from a common superclass. If a common superclass does not already exist, create one, and move the common responsibilities to it. After all, such a class is demonstrably usefulyou have already shown that the responsibilities will be inherited by some classes. Isn't it conceivable that a later extension of your system might add a new subclass that will support those same responsibilities in a new way? This new superclass will probably be an abstract class.[10] [10] [Wirfs-Brock90], p. 113 Listing 10-12 shows how the attributes of LinearObject can be used by an unanticipated class: Ray. A Ray is substitutable for a LinearObject, and no user of LinearObject would have any trouble dealing with it. Listing 10-12. Ray.cs
|
Категории