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