The handle/body idiom

Instructor's Guide


intro polymorphism idioms patterns events summary, Q/A, literature
The handle/body class idiom, originally introduced in  [Coplien92], separates the class defining a component's abstract interface (the handle class) from its hidden implementation (the body class). All intelligence is located in the body, and (most) requests to the handle object are delegated to its implementation.

In order to illustrate the idiom, we use the following class as a running example:


  class A { 
A -- naive
public A() { } public void f1() { System.out.println("A.f1"); f2(); } public void f2() { System.out.println("A.f2"); } };

slide: Running example

The implementation of A is straightforward and does not make use of the handle/body idiom. A call to the f1() member function of A will print a message and make a subsequent call to f2(). Without any modification in the behavior of A's instances, it is possible to re-implement A using the handle/body idiom. The member functions of class A are implemented by its body, and A is reduced to a simple interface class:
  class A { 
A
public A() { body = new BodyOfA(this); } protected A(int x) { } public void f1() { body.f1(); } public void f2() { body.f2(); } public void f3() { System.out.println("A.f3"); } private A body; };

slide: Interface: A

Note that the implementation of A's body can be completely hidden from the application programmer. In fact, by declaring A to be the superclass of its body class, even the existence of a body class can be hidden. If A is a class provided by a shared library, new implementations of its body class can be plugged in, without the need to recompile dependent applications:
  class BodyOfA extends A { 
BodyOfA -- naive
public BodyOfA() { super(911); } public void f1() { System.out.println("A.f1"); f2(); } public void f2() { System.out.println("A.f2"); } };

slide: Naive: BodyOfA

In this example, the application of the idiom has only two minor drawbacks. First, in the implementation below, the main constructor of A makes an explicit call to the constructor of its body class. As a result, A's constructor needs to be changed whenever an alternative implementation of the body is required. The Abstract Factory pattern described in  [GOF94] may be used to solve this problem in a generic and elegant way. Another (aesthetic) problem is the need for the dummy constructor to prevent a recursive chain of constructor calls.

But the major drawback of the handle/body idiom occurs when deriving a subclass of A which partially redefines A's virtual member functions. Consider this definition of a derived class C:


  class C extends A { 
C
public void f2() { System.out.println("C.f2"); } };

slide: Usage: C

Try to predict the output of a code fragment like:
    C c = new C; c.f1();    
  

slide: Example: calling C

The behavior of instances of C does indeed depend on whether the hidden implementation of its base class A applies the handle/body idiom or not! If it does, the output will be A.f1() A.f2(). because the indirect call to f2() in f1() will (unexpectedly) not call the redefined version of f2(). The original definition of A would of course yield A.f1() C.f2(). but this can only be obtained by deriving C directly from the (hidden) body class. Note that this is an illustration of one of the main drawbacks of the OOP paradigm: the inability to change base classes at the top of a hierarchy without introducing errors in derived classes.

Explicit invocation context

In both implementations of A, the call to f2() in f1() is an abbreviation of this.f2(). However, in the first, naive implementation of A, the implicit this reference refers to the handle object (which can be an instance of a derived class). In contrast, this in the BodyOfA will refer to the body object. As a consequence, the body object is unable to make calls to functions redefined by classes derived from the base class A. We use the term invocation context to denote a reference to the context in which the original request for a specific service is made, and represent this by a pointer to the handle object. In other words, the handle object needs a pointer to its body to be able to delegate its functionality, and, symmetrically, the body needs a pointer to the handle in order to be able to use any redefined virtual functions. The body can be redefined as:
  class BodyOfA extends A { 
BodyOfA
public BodyOfA(A h) { super(911); handle = h; } public void f1() { System.out.println("A.f1"); handle.f2(); } public void f2() { System.out.println("A.f2"); } A handle;
reference to invocation context
};

slide: Handle/Body: BodyOfA

The new body class is aware of the fact that it is implementing services which are accessed via the handle object. Consequently, it can use this information and is able to make calls to functions which might be redefined by descendants of A.

Note that this solution does require some programming discipline: all (implicit) references to the body object should be changed into a reference to the invocation context. Fortunately, this discipline is only required in the body classes of the implementation hierarchy.



slide: Separating interface hierarchy and implementation

Descendants of the handle classes in the public interface hierarchy can share and redefine code implemented by the hidden body classes in a completely transparent way, because all code sharing takes place indirectly, via the interface provided by the handle classes. However, even other body classes will typically share code via the handle classes. Also derived classes can use the handle/body idiom, as depicted in slide handle-body.