Jump to content
xisto Community
Sign in to follow this  
evought

The Holywood Principle Dont Call Us, Well Call You

Recommended Posts

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:

 

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:

 

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:

 

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].

 

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:

 

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.

 

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:

 

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://forums.xisto.com/no_longer_exists/. David Van Camp. 2002

 

[FV1994] Family Values: A Behavioral Notion of Subtyping

. Barbara Liskov, Jeanette M. Wing. October 1994
Edited by evought (see edit history)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×
×
  • Create New...

Important Information

Terms of Use | Privacy Policy | Guidelines | We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.