Loading...


bookmark - The Holywood Principle Don't Call Us, We'll Call You

The Holywood Principle - Don't Call Us, We'll Call You

 
 Discussion by evought with 0 Replies.
 Last Update: March 29, 2006, 1:05 pm
 
bookmark - The Holywood Principle Don't Call Us, We'll Call You  
Quickly Post to The Holywood Principle Don't Call Us, We'll Call You w/o signup Share Info about The Holywood Principle Don't Call Us, We'll Call You using Facebook, Twitter etc. email your friend about The Holywood Principle Don't Call Us, We'll Call You Print
Reply / Comment New Discussion / Topic Share / Bookmark E-Mail a Friend Print

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 API to subclasses is tricky but important. The "Override and Call-Inherited" idiom is commonly found in Object Oriented APIs, 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 API 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(..) first draws its own borders, then delegates to its subclasses via the pure virtual 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

   Wed Mar 29, 2006    Reply         

Quickly Post to The Holywood Principle Don't Call Us, We'll Call You w/o signup Share Info about The Holywood Principle Don't Call Us, We'll Call You using Facebook, Twitter etc. email your friend about The Holywood Principle Don't Call Us, We'll Call You Print
Reply / Comment New Discussion / Topic Share / Bookmark E-Mail a Friend Print

Similar Topics:

Call Of Duty 2

First Words Call of Duty. Released in a time when WW2 shooters were announced one after another, it seemed like just one of them, untill the first people got a chance to play it. The action was intense, and really made you feel like you were in the heat of the battle. Chaos, large numbers of s ...more

   06-Jan-2006    Reply         

Calling Of Functions Between Mulitp...

I have a page that requires many Javascript functions. In order to make the coding easier to read and edit, I decided to seperate them into 3 Javascript files. Two files will each do a specific job. One file will have the shared functions that both other 2 files will need to use. They are all linked ...more

   19-Apr-2006    Reply         

Call Of Duty 4

Well I picked up Call of Duty 4 on a whim on Sunday since I heard it was a pretty good game, though I had not formally researched reviews and whatnot... I beat the Campaign mode Tuesday, and I would have beaten it sooner if I hadn't gotten stuck on one of the later levels... Here we go: ...more

   09-Apr-2008    Reply         

Handling Errors To Help You In Debugging finding the source of your errors   Handling Errors To Help You In Debugging finding the source of your errors (2) (6) Writing ISRs (Interrupt Service Routines) Interrupt Service Routines  Writing ISRs (Interrupt Service Routines) Interrupt Service Routines