|
|
|
| Web Hosting Guide |
![]() ![]() |
The Holywood Principle, Don't Call Us, We'll Call You |
Mar 29 2006, 09:05 PM
Post
#1
|
|
|
Premium Member Group: Members Posts: 213 Joined: 3-October 05 From: Missouri Member No.: 8,888 myCENTs:27.93 |
Welcome to Hollywood: Don't Call Us, We'll Call You
It is a little appreciated fact that a class presents two APIs: one to its callers and one to its subclasses. Presenting a coherent, easy-to-use, and hard-to-misuse [ACRONYM="Application Programming Interface"]API[/ACRONYM] to subclasses is tricky but important. The "Override and Call-Inherited" idiom is commonly found in Object Oriented [ACRONYM="Application Programming Interface"]API[/ACRONYM]s, nearly anywhere virtual classes are present. While this idiom is useful and works, it is error prone and better methods exist. In this article the "Hollywood Principle" is used to write safer code. C++ and Java implementations are discussed. Override and Call-Inherited In many APIs, the documentation for a method contains a note like the following: "Override this in your subclass and call the inherited method." To take a simple example: CODE class DrawableItem { // ... other members virtual void draw(Context context) { drawBorder(context); update(); } }; The idea is to subclass and create your own DrawableItem. When you do so, you override draw(..) and call the existing version: CODE class MyDrawableItem : public DrawableItem { void draw(Context context) { drawUpsideDownPandaOrWhatever(context); DrawableItem::draw(context); } }; This is a simple way for the base class to provide some common functionality while still allowing subclasses considerable freedom. Leaving draw(..) as a pure virtual in the base class would require every subclass to remember to draw its own borders and call update when finished. I see "Override-and-Call-Inherited" code on a daily basis. The Problem While simple, this idiom replaces one problem with several others. Consider the following: CODE class MyDrawableItem1 : public DrawableItem { void draw(Context context) { drawASquare(context); // oops } }; class MyDrawableItem2 : public DrawableItem { void draw(Context context) { DrawableItem::draw(context); // oops drawACircle(context); } }; class MyDrawableItem3 : public MyDrawableItem2 { void draw(Context context) { drawACircleInTheSquare(context); DrawableItem::draw(context); // oops } }; class MyDrawableItem4 : public MyDrawableItem { // oops }; All of these mistakes are simple to make, especially when cutting and pasting code to create a number of related subclasses. The problems with MyDrawableItem2 and MyDrawableItem3 are subtle and may be hard to diagnose. In Drawableitem2, the superclass method is called before the specialized subclass code. This will result in a call to update(..), presumably to refresh the screen, before the subclass has finished drawing. The appropriate order is not often given in [ACRONYM="Application Programming Interface"]API[/ACRONYM] documentation, making the problem even harder to find. A bug of this kind in a real drawing package can be very difficult to diagnose due to inconsistent behavior. The underlying graphics package will try to optimize calls to avoid excessive redrawing and the class may work as intended most of the time. In MyDrawableItem3, there is an additional layer of subclassing, but the wrong superclass method is called. This type of error often results from a refactoring of the base class. The original call was correct, but the name of the direct superclass has changed over time. This particular problem can be avoided in Java applications by using the super keyword which refers to the direct superclass, regardless of what it is. The last mistake, in MyDrawableItem4, is a consequence of draw not being pure virtual. The compiler will not force the subclass to provide its own draw(..) method. This also means that the compiler may not warn you if you get the signature of draw(..) wrong in your subclass! It is important to note that the ability of the subclass to not call the inherited method is a vehicle to violate the Liskov Substitution Principle [FV1994], which states that any child class reference must be a valid and indistinguishable substitute for a parent class reference. This principle is one of the foundations of Object-Oriented Programming and is critical to the ability to program generic algorithms which operate over collections of polymorphic objects. Loopholes like that provide by DrawableItem are commonly exploited to make subclasses which behave in a way not envisioned by the superclass designer. Sometimes, this can allow a programmer to work around a serious defect or limitation in an API; often a lazy programmer can use it to write code which is obtuse and confusing. A prudent API designer will close the loophole while at the same time providing more constructive ways to extend the class. A Better Solution The solution, in essence, is to divide draw(..) into two separate methods, one of which cannot be overridden and one of which must be. This is a specific application of the Template Method pattern [GOF] [OOPD:TM]. CODE class SafeDrawableItem { // ... other members public: void draw(Context context) { drawBorder(context); itemDraw(context); update(); } protected: virtual void itemDraw(Context context) = 0; }; SafeDrawableItem::draw(..)[I] first draws its own borders, then delegates to its subclasses via the pure virtual [I]itemDraw(..). Last, a call is made to update the screen. Notice that draw(..) is not virtual and is not to be overridden; instead, draw(..) provides a template and itemDraw(..) will fill in the details: CODE class MySafeDrawableItem : public SafeDrawableItem { // ... other members protected: void itemDraw(Context context) { drawMyOwnThing(context); } }; itemDraw is a bit simpler than the old draw(..) implementation. There is no need to call the superclass method in each subclass, which should reduce the temptation to cut-and-paste. itemDraw(..) is always called and always at the appropriate time, so call sequence is no longer an issue and the name of base class can be changed without breaking subclass code. Forgetting to implement itemDraw(..) or mistyping the method signature is now flagged as a compiler error. A subclass can still attempt to override draw(..), but this will have little effect if the use of DrawableItem is primarily through pointers or references to the base class. Since draw(..) is not virtual, the base class method will be called and the subclass method will be ignored. Some C++ compilers will warn about this case. Java's final keyword makes this explicit and tells the compiler that draw(..) may not be overridden. CODE public abstract class JavaDrawableItem { // ... other members public final void draw(Context context) { drawBorder(context); itemDraw(context); update(); } protected abstract void itemDraw(Context context); } A last advantage to this approach is that there is a clear and appropriate point to handle exceptions which is otherwise difficult in the original code: CODE class MySafeDrawableItem { // Other stuff ... void draw(Context context) { drawBorder(context); try { itemDraw(context); } catch (SubclassDrawingException& e) { // Do something appropriate } update(); } }; This top-down control structure is sometimes referred to as the Hollywood Principle [GOF] ("Don't call us, we'll call you"). The TemplateMethod pattern gives the base class rigid control over the sequence of events while the subclass provides specialized behavior at key points. Here a pair of methods is shown, but there is no reason that draw(..) cannot call multiple virtual methods, some of which may have default implementations. The point is, when you want to ensure that something happens in a subclass, do it yourself, do not put a request in the documentation. References [GOF] Design Patterns: Elements of Reusable Object-Oriented Software. Erich Gama, Richard Helm, John Vlissides, Ralph Johnson. Addison Wesley Longman, Inc. October 1994. ISBN: 0-201633612 [OOPD:TM] The Object-Oriented Pattern Digest. Template Method http://patterndigest.com/patterns/TemplateMethod.html. David Van Camp. 2002 [FV1994] Family Values: A Behavioral Notion of Subtyping. Barbara Liskov, Jeanette M. Wing. October 1994 This post has been edited by evought: Apr 10 2006, 01:22 PM |
|
|
|
![]() ![]() |
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:
|
Lo-Fi Version | Time is now: 21st March 2010 - 10:04 AM |
© 2010 AstaHost: Free Web Hosting & Technical Discussion, Free Web Hosting. a member of xisto.
Powered by Invision Board. Skin: IPB Forum Skins
Expand / Collapse Navigation



Mar 29 2006, 09:05 PM


