C# Design Patterns: The Composite Pattern
- An Implementation of a Composite
- The Employee Classes
- Building the Employee Tree
- Consequences of the Composite Pattern
- Other Implementation Issues
Frequently, programmers develop systems in which a component may be either an individual object or a collection of objects. The Composite pattern is designed to accommodate both cases. You can use the Composite to build part-whole hierarchies or to construct data representations of trees. In summary, a composite is a collection of objects, any one of which may be either a composite or just a primitive object. In tree nomenclature, some objects may be nodes with additional branches and some may be leaves.
The problem that develops is the dichotomy between having a single, simple interface to access all the objects in a composite and the ability to distinguish between nodes and leaves. Nodes have children and can have children added to them, whereas leaves do not at the moment have children, and in some implementations they may be prevented from having children added to them.
Some authors have suggested creating a separate interface for nodes and leaves where a leaf could have the methods, such as the following.
public string getName(); public float getValue();
And a node could have these additional methods.
public ArrayList elements(); public Node getChild(string nodeName); public void add(Object obj); public void remove(Object obj);
This then leaves us with the programming problem of deciding which elements will be which when we construct the composite. However, Design Patterns suggests that each element should have the same interface whether it is a composite or a primitive element. This is easier to accomplish, but we are left with the question of what the getChild operation should accomplish when the object is actually a leaf.
C# can make this quite easy for us, since every node or leaf can return an ArrayList of the child nodes. If there are no children, the count property returns zero. Thus, if we simply obtain the ArrayList of child nodes from each element, we can quickly determine whether it has any children by checking the count property.
Just as difficult is the issue of adding or removing leaves from elements of the composite. A nonleaf node can have child-leaves added to it, but a leaf node cannot. However, we would like all of the components in the composite to have the same interface. We must prevent attempts to add children to a leaf node, and we can design the leaf node class to raise an error if the program attempts to add to such a node.
An Implementation of a Composite
Let's consider a small company that began with a single person. He was, of course, the CEO, although he may have been too busy to think about it at first. Then he hired a couple of people to handle the marketing and manufacturing. Soon each of them hired some additional assistants to help with advertising, shipping, and so forth, and they became the company's first two vice-presidents. As the company's success increased, the firm continued to grow until it had the organizational chart in Figure 16-1.
Figure 16-1. A typical organizational chart
If the company is successful, each of these company members receives a salary, and we could at any time ask for the cost of the control span of any employee to the company. We define this control cost as the salary of that person and those of all subordinates. Here is an ideal example for a composite.
The cost of an individual employee is simply his or her salary (and benefits).
The cost of an employee who heads a department is his or her salary plus those of subordinates.
We would like a single interface that will produce the salary totals correctly whether the employee has subordinates or not.
float getSalaries(); //get salaries of all
At this point, we realize that the idea of all Composites having the same standard method names in their interface is probably unrealistic. We'd prefer that the public methods be related to the kind of class we are actually developing. So rather than have generic methods like getValue, we'll use getSalaries.