Contents
Composite
Compose objects into tree structures to represent whole-part hierarchies.
Composite lets clients treat individual objects and compositions of objects
uniformly.
Places where Composites might be used:
-
Product Assemblies: We have products A, B, C, D, E, etc. We also have product X
made up of 2 A�s, a C and a D. We also have Product Y made up of an H, a J and
two X�s.
-
GUIs nearly always have objects which in turn contain other objects (e.g. a
Form containing a Panel containing a TextBox and a GroupBox containing radio
buttons, etc).
-
A compound Document which contains other objects, each of which can be a
compound document.
Composites can be traversed recursively, e.g.:
private void Traverse(XmlNode aNode) {
// do something with the node
// and then the children
foreach(XmlNode childNode in aNode.ChildNodes) {
Traverse(childNode);
}
}
This is almost their defining feature in my opinion.
The canonical pattern has an abstract Component class with an Add and a Remove
method, each taking a Component object as a parameter; it also has a method for
getting a child or a list of all children. It also has one or more methods
which do something specific to the individual objects (e.g. for the GUI example
a BackColor property).
Composite inherits from Component, and stores child Components. It defines the
behaviour of components which can have children (e.g. Form, Panel)
Leaf inherits from Component but has no children. It defines the behaviour of
Components that cannot have children (e.g. TextBox, Label).
Do we really need separate Leaf and Composite classes? Should Composite inherit
from Leaf, and do away with Component?
Consequences:
Client code has to expect the Component, not the Leaf; but this is still simple
code. GoF warn that this pattern can become overly general.
From
http://home.earthlink.net/~huston2/dp/composite.html, various comments.
Note particularly the author of the last one � one of the GoF.
-
Being able to treat a heterogeneous collection of objects atomically (or
transparently) requires that the "child management" interface be
defined at the root of the Composite class hierarchy (the abstract Component
class). However, this choice costs you safety, because clients may try to do
meaningless things like add and remove objects from leaf objects. On the other
hand, if you "design for safety", the child management interface is
declared in the Composite class, and you lose transparency because leaves and
Composites now have different interfaces. [Bill Burcham]
-
Smalltalk implementations of the Composite pattern usually do not have the
interface for managing the components in the Component interface, but in the
Composite interface. C++ implementations tend to put it in the Component
interface. This is an extremely interesting fact, and one that I often ponder.
I can offer theories to explain it, but nobody knows for sure why it is true.
[Ralph Johnson]
-
My Component classes do not know that Composites exist. They provide no help
for navigating Composites, nor any help for altering the contents of a
Composite. This is because I would like the base class (and all its
derivatives) to be reusable in contexts that do not require Composites. When
given a base class pointer, if I absolutely need to know whether or not it is a
Composite, I will use dynamic_cast to figure this out. In those cases where
dynamic_cast is too expensive, I will use a Visitor. [Robert Martin]
-
Composite doesn't force you to treat all Components as Composites. It merely
tells you to put all operations that you want to treat "uniformly" in
the Component class. If add, remove, and similar operations cannot, or must
not, be treated uniformly, then do not put them in the Component base class.
Remember, by the way, that each pattern's structure diagram doesn't define the
pattern; it merely depicts what in our experience is a common realization
thereof. Just because Composite's structure diagram shows child management
operations in the Component base class doesn't mean all implementations of the
pattern must do the same. [John Vlissides]
[This document was original the backup for a presentation which split the
attendees into small groups (2 or 3) and asked them to implement in more or
less detail the following specifications.
There is an implementation of Composite below the two exercises.]
Spec I
A Retailer has an existing Stock Control and Order Processing system (written in
C#, of course). This has an existing Product class which contains properties
for Description, Price, Cost, and a ProductType (a class containing Description
and Code).
He wishes to introduce some new products by packaging existing ones into �kits�
� for example including two pairs of ear-muffs with a toy drum. The cost of a
kit will be �1 more than the sum of the cost of the individual products. The
Sale Price will be the sum of the individual Prices, after a percentage has
been added on to the individual product�s Price (where the percentage will
depend on the ProductType of the individual product); however if this
calculated price is less than 115% of the Kit�s cost, the Price will be 115% of
the Kit�s cost.
Spec II
Assuming that you were writing a system from scratch for the above problem, how
would you implement it differently?
Sample implementation of Composite:
using System;
using System.Collections;
namespace GofPatterns.Composite {
public class Composite {
private ArrayList children;
protected object whatThisNodeIs;
public Composite(object whatThisNodeIs) {
children = new ArrayList();
this.whatThisNodeIs = whatThisNodeIs;
}
public object BaseObject {
get { return whatThisNodeIs; }
}
public override string ToString() {
return string.Format(
"Composite containing '{0}' and {1} children",
whatThisNodeIs.ToString(), this.Count
);
}
public int Count {
get {
int iCount = 0;
foreach (Composite c in children) {
iCount += c.Count + 1;
}
return iCount;
}
}
public void Add(Composite toAdd) {
children.Add(toAdd);
}
public void Remove(Composite toRemove) {
if (children.Contains(toRemove)) {
children.Remove(toRemove);
}
}
public void RemoveAt(int indexToRemove) {
if (indexToRemove < children.Count) {
children.RemoveAt(indexToRemove);
}
}
public ArrayList Children {
get { return children; }
}
}
}
The client would look like this. The following code creates and fills a composite and then shows it in a WinFowms TreeView.
private GofPatterns.Composite.Composite composite;
private void MakeAndShowComposite() {
composite = new Composite("Top level composite");
for (int i = 0; i < 3; i++) {
Composite child =
new Composite("Child " + i.ToString());
composite.Add(child);
for (int j = 0; j < 5; j++) {
Composite child2 =
new Composite("Child " +
j.ToString() + " of " + i.ToString());
child.Add(child2);
}
}
ShowTV();
}
///
/// A TreeView is in fact a composite of TreeNodes,
/// so this is a good way of showing one :)
///
private void ShowTV() {
tv1.BeginUpdate();
tv1.Nodes.Clear();
TreeNode topNode = new TreeNode(composite.ToString());
topNode.Tag = composite;
tv1.Nodes.Add(topNode);
ShowNodeChildren(topNode);
tv1.ExpandAll();
tv1.EndUpdate();
}
private void ShowNodeChildren(TreeNode parent) {
GofPatterns.Composite.Composite comp =
parent.Tag as GofPatterns.Composite.Composite;
foreach (GofPatterns.Composite.Composite c in comp.Children) {
TreeNode node = new TreeNode(c.ToString());
node.Tag = c;
parent.Nodes.Add(node);
ShowNodeChildren(node);
}
}