pointer-image   32   Substitute by Contract

 

“Deep inheritance hierarchies are great. If you need functionality from some other class, just inherit from it! And don’t worry if your new class breaks things; your callers can just change their code. It’s their problem, not yours.”

images/devil.png

A key way to keep systems flexible is by letting new code take the place of existing code without the existing code knowing the difference. For instance, you might need to add a new type of encryption to a communications infrastructure or implement a better search algorithm using the same interface. As long as the interface remains the same, you are free to change the implementation without changing any other code. That’s easier said than done, however, so we need a little bit of guidance to do it correctly. For that, we’ll turn to Barbara Liskov.

Liskov’s Substitution principle Data Abstraction and Hierarchy [Lis88] tells us that “Any derived class object must be substitutable wherever a base class object is used, without the need for the user to know the difference.” In other words, code that uses methods in base classes must be able to use objects of derived classes without modification.

What does that mean exactly? Suppose you have a simple method in a class that sorts a list of strings and returns a new list. You might invoke it like this:

 
utils = new BasicUtils();
 
...
 
sortedList = utils.sort(aList);

Now suppose you subclass the BasicUtils class and make a new sort method that uses a much better, faster sort algorithm:

 
utils = new FasterUtils();
 
...
 
sortedList = utils.sort(aList);

Note the call to sort is the same; a FasterUtils object is perfectly substitutable for a BasicUtils object. The code that calls utils.sort could be handed a utils of either type, and it would work fine.

But if you made a subclass of BasicUtils that changed the meaning of sort—returning a list that sorted in reverse order, perhaps—then you’ve grossly violated the Substitution principle.

images/Delegation.png

Figure 5. Delegation versus inheritance

To comply with the Substitution principle, your derived class services (methods) should require no more, and promise no less, than the corresponding methods of the base class; it needs to be freely substitutable. This is an important consideration when designing class inheritance hierarchies.

Inheritance is one of the most abused concepts in OO modeling and programming. If you violate the Substitution principle, your inheritance hierarchy may still provide code reusability but will not help with extensibility. The user of your class hierarchy may now have to examine the type of the object it is given in order to know how to handle it. As new classes are introduced, that code has to constantly be reevaluated and revised. That’s not an agile approach.

But help is available. Your compiler may help you enforce the LSP, at least to some extent. For example, consider method access modifiers. In Java, the overriding method’s access modifier must be the same or more lenient than the modifier of the overridden method. That is, if the base method is protected, the derived overriding method must be protected or public. In C# and VB .NET, the access protection of the overridden method and the overriding method are required to be the same.

Consider a class Base with a method findLargest that throws an IndexOutOfRangeException. Based on the documentation, a user of this class will prepare to catch that exception if thrown. Now, assume you inherit the class Derived from Base, override the method findLargest, and in the new method throw a different exception. Now, if an instance of Derived is used by code expecting an object of class Base, that code may receive an unexpected exception. Your Derived class is not substitutable wherever Base is used. Java avoids this problem by not allowing you to throw any new kind of checked exceptions from the overriding methods, unless the exception itself derives from one of the exception classes thrown from the overridden method (of course, for unchecked exceptions such as RuntimeException, the compiler won’t help you).

Unfortunately, Java violates the Substitution principle as well. The java.util.Stack class derives from the java.util.Vector class. If you (inadvertently) send an object of Stack to a method that expects an instance of Vector, the elements in the Stack can be inserted or removed in an order inconsistent with its intended behavior.

When using inheritance, ask yourself whether your derived class is substitutable in place of the base class. If the answer is no, then ask yourself why you are using inheritance. If the answer is to reuse code in the base class when developing your new class, then you should probably use composition instead. Composition is where an object of your class contains and uses an object of another class, delegating responsibilities to the contained object (this technique is also known as delegation).

Figure 5, Delegation versus inheritance shows the difference. Here, a caller invoking methodA in Called Class will get it automatically from Base Class via inheritance. In the delegation model, the Called Class has to explicitly forward the method call to the contained delegate.

When should you use inheritance versus delegation?

  • If your new class can be used in place of the existing class and the relationship between them can be described as is-a, then use inheritance.

  • If your new class needs to simply use the existing class and the relationship can be described as has-a or uses-a, then use delegation.

You may argue that in the case of delegation you have to write lots of tiny methods that route method calls to the contained object. In inheritance, you don’t need these, because the public methods of the base class are readily available in the derived class. By itself, that’s not a good enough reason to use inheritance.

You can write a good script or a nice IDE macro to help you write these few lines of code or use a better language/environment that supports a more automatic form of delegation (Ruby does this nicely, for instance).

images/angel.png

Extend systems by substituting code.

Add and enhance features by substituting classes that honor the interface contract. Delegation is almost always preferable to inheritance.

What It Feels Like

It feels sneaky; you can sneak a replacement component into the code base without any of the rest of the code knowing about it to achieve new and improved functionality.

Keeping Your Balance

  • Delegation is usually more flexible and adaptable than inheritance.

  • Inheritance isn’t evil, just misunderstood.

  • If you aren’t sure what an interface really promises or requires, it will be hard to provide an implementation that honors it.

Footnotes

[25]

That’s venti to you Starbucks fans.

[26]

Yeah, that’s not a bald spot; it’s a solar panel for a coding machine….

[27]

In The Wizard of Earthsea books, for example, knowing the true name of something gives one complete power over it. Magical control via naming is a fairly common theme in literature and mythology and has a similar effect in software.

[28]

XML documents are much like humans—they are cute and fun to deal with when they’re small but can get really annoying as they grow bigger.

[29]

Donald Knuth’s pithy summary of Hoare’s dictum (Literate Programming [Knu92])

[30]

Kent Beck introduced the driving analogy—and the importance of steering—in Extreme Programming Explained: Embrace Change [Bec00].

[31]

You might call this a “Spaghetti OOs” system.

[32]

One reviewer told us of a system that needed sixteen team members and six managers to add one field to a form. That’s a pretty clear warning sign of a noncohesive system.

[33]

http://www.javaguy.org/papers/demeter.pdf

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.113.188