We'll give a brief sketch of the implementation of class and object adapters for the Motivation example beginning with the classes Shape and
TextView.
~cpp
class Shape {
public:
Shape ();
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
virtual Manipulator* CreateManipulator () const;
};
class TextView {
public:
TextView ();
void GetOrigin (Coord& x, Coord& y) const;
void GetExtent (Coord& width, Coord& height) const;
virtual bool IsEmpty () const;
Shape assumes a bounding box defined by its opposing corners. In contrast,
TextView is defined by an origin, height, and width. Shape also defines a
CreateManipulator operation for creating a Manipulator object, which knowns how to animate a shape when the user manipulates it.
TextView has no equivalent operation. The class
TextShape is an adapter between these different interfaces.
A class adapter uses multiple inheritance to adapt interfaces. The key to class dapters is to use one inheritance branch to inherit the interface and another branch to inherit the implementation. The usual way to make this distinction in C++ is to inherit the interface publicly and inherit the implementation privately. We'll use this convention to define the
TextShape adapter.
~cpp
class TextShape : public Shape, private TextView {
public:
TextShape ();
virtual void BoundingBox (Point& bottomLeft, Point& topRight) const;
virtual bool IsEmpty () const;
virtual Manipulator* CreateManipulator () const;
};
The
BoundingBox operation converts
TextView's interface to conform to Shape's.
~cpp
void TextShape::boundingBox (Point& bottomLeft, Point& topRight) const {
Coord bottom, left, width, height;
GetOrigin (bottom, left);
GetExtent (width, height);
bottomLeft = Point (bottom, left);
topRight = Point (bottom + height, left + width);
The
IsEmpty operations demonstrates the direct forwarding of requests common in adapter implementations:
~cpp
bool TextShape::ImEmpty () const {
return TextView::IsEmpty ();
}
Finally, we define
CreateManipulator (which isn't supported by
TextView) from scratch. Assume we've already implemented a
TextManipulator class that supports manipulation of a
TextShape.
~cpp
Manipulator* TextShape::CreateManipulator () const {
return new TextManipulator (this);
The object adapter uses object composition to combine classes with different interfaces. In this approach, the adapter
TextShape maintains a pointer to
TextView.
~cpp
class TextShape : public Shape {
public:
TextShape (TextView*);
virtual void BoundingBox (Point& bottomLeft, Point& topRight) const;
virtual bool IsEmpty () const;
virtual Manipulator* CreateManipulator () const;
private:
TextView* _text;
};
TextShape must initialize the pointer to the
TextView instance, and it does so in the constructor. It must also call operations on its
TextView object whenever its own operations are called. In this example, assume that the client creates the
TextView object and passes it to the
TextShape constructor.
~cpp
TextShape::TextShape (TextView* t) {
_text = t;
}
void TextShape::BoundingBox (
Point& bottomLeft, Point& topRight
) const {
Coord bottom, left, width, height;
_text->GetOrigin (bottom, left);
_text->GetExtent (width, height);
bottomLeft = Point (bottom, left);
topRight = Point (bottom + height, left + width);
}
bool TextShape::IsEmpty () const {
return _text->IsEmpty ();
}
CreateManipulator's implementation doesn't change from the class adapter version, since it's implemented from scratch and doesn't reuse any existing
TextView functionality.
~cpp
Manipulator* TextShape::CreateManipulator () const {
return new TextManipulator (this);
}
Compare this code the class adapter case. The object adapter requires a little more effort to write, but it's more flexible. For example, the object adapter version of
TextShape will work equally well with subclasses of
TextView -- the client simply passes an instance of a
TextView subclass to the
TextShape constructor.