The Composite Pattern
The composite pattern composes objects into tree structures to represent part-whole hierarchies. The composite pattern lets client code treat individual objects and compositions of objects uniformly. For example, a composite shape is made up of several individual shapes such as rectangles and ellipses. The composite pattern allows simple shapes and complex shapes to be handled the same way.
The
CComposite template class provides an implementation of the composite pattern. It maintains a list of child objects that are accessed through methods such as
AddChild(),
RemoveChild(), and
GetChildrenCount(). In addition to having a list of children, each composite object maintains a pointer to its parent. The declaration of this templated class is shown in
Example 10Example 10 – CComposite class declaration
template <typename _Component, const GUID* _guid>
class CComposite:
public IQueryGuid
{
<…>
};
The first parameter passed into the CComposite template is the component type, which determines the type of parent and child objects in the composite. Objects in the tree are accessed using that type. For example, the declaration of the GetParent() method returns a pointer to a _Component object, not a CComposite<> object:
_Component* GetParent() const;
The second parameter in the template is a GUID that will identify the composite interface within the set of interfaces implemented by the component classes. The composite implementation does not assume an inheritance relationship between the
CComposite<> class and the
_Component class. Rather than an implicit conversion, casting from one to the other is performed using the
guid_cast<> mechanism, standard in SFL. Whenever the
CComposite<> interface is needed in some operation, a
guid_cast<> is performed using the GUID passed in the second template parameter. Derived classes are responsible for providing an adequate interface map that allows this
guid_cast<> call to succeed. All classes that mix in the
CComposite<> template among their base classes are indirectly deriving from
IQueryGuid, as seen earlier in
Example 10.
Example 11 uses the
CComposite class to compose complex shapes from simple shapes. The sample defines an entire class hierarchy that mixes in the composite pattern for all of its classes.
Example 11 – Composing complex shapes
// Abstract base class for all shapes
class __declspec(uuid("ABDC16B0-5195-11d3-4D94-00C06F92F286")) Shape
{
public:
virtual Draw(CDC* pDC) = 0;
};
// Define a GUID to provide a way to downcast any shape to a
// composite shape
class __declspec(uuid("ABDC16B1-5195-11d3-4D94-00C06F92F286"))
CompositeShape;
// Default implementation for composite shapes
class CompositeShapeBase :
public Shape,
public CComposite<Shape, __uuidof(CompositeShape)>
{
public:
virtual Draw(CDC* pDC)
{
// Iterate over list of contained shapes
// using the CComposite<> interface facilities
. . .
// Draw each child shape
. . .
}
BEGIN_GUID_MAP(CompositeShapeBase)
GUID_ENTRY_IID(__uuidof(CompositeShape), _compositeBase)
GUID_ENTRY_IID(__uuidof(Shape), _compositeBase)
END_GUID_MAP()
};
// A normal composite shape
class CompositeShapeNormal : public CompositeShapeBase
{
public:
virtual Draw(CDC* pDC)
{
// Draw shapes front to back
}
};
// A reverse Z-order composite shape
class CompositeShapeRev : public CompositeShapeBase
{
public:
virtual Draw(CDC* pDC)
{
// Reverse order and draw shapes back to front
}
};
// Simple shapes
class Polygon : public Shape
{
public:
virtual Draw(CDC* pDC)
{
// Draw a polygon
. . .
}
BEGIN_GUID_MAP(ShapeImpl)
GUID_ENTRY_IID(__uuidof(Shape), _compositeBase)
END_GUID_MAP()
};
class Rectangle : public Shape
{
public:
virtual Draw(CDC* pDC)
{
// Draw a rectangle
}
BEGIN_GUID_MAP(ShapeImpl)
GUID_ENTRY_IID(__uuidof(Shape), _compositeBase)
END_GUID_MAP()
};
Example 12 sets up a composite tree with three descendents, one of which is, in turn, a composite.
Example 12 – Setting up a composite tree
typedef CComposite<Shape, __uuidof(CompositeShape)> CompositeShape;
Shape* pRootShape = new CompositeShapeNormal;
CompositeShape* pComposite = guid_cast<CompositeShape>(pRootShape);
pComposite->AddChild(new Rectangle);
pComposite->AddChild(new Polygon);
Shape* pSubShape = new CompositeShapeRev;
CompositeShape* pSubComposite =
guid_cast<CompositeShape>(pSubShape);
pSubComposite->AddChild(new Rectangle);
pComposite->AddChild(pSubComposite);
NOTE >> CComposite<> does not make any assumptions about the allocation of the children, and therefore does not deallocate them upon destruction of the object.
In
Example 12, then, the objects allocated using the
new() operator should be deallocated by some external agent before the root of the composite gets destroyed. Otherwise, a memory leak occurs.