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.
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 knows how to animate a shape when the user manipulates it. (CreateManipulator is an example of a Factory Method.) TextView has no equivalent operation. The class TextShape is an adapter between these different interfaces.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; };
A class adapter uses multiple inheritance to adapt interfaces. The key to class adapters 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.
The BoundingBox operation converts TextView's interface to conform to Shape's.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 IsEmpty operation demonstrates the direct forwarding of requests common in adapter implementations: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); }
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.bool TextShape::IsEmpty () const { return TextView::IsEmpty(); }
The object adapter uses object composition to combine classes with different interfaces. In this approach, the adapter TextShape maintains a pointer to TextView.Manipulator* TextShape::CreateManipulator () const { return new TextManipulator(this); }
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: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; };
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.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(); }
Compare this code to 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.Manipulator* TextShape::CreateManipulator () const { return new TextManipulator(this); }