© Jennifer M. Kohnke
A composite is a euphemism for a lie. It’s disorderly. It’s dishonest and it’s not journalism.
—Fred W. Friendly, 1984
The COMPOSITE pattern is a very simple pattern that has significant implications. The fundamental structure of the COMPOSITE pattern is shown in Figure 31-1. Here, we see a hierarchy based on shapes. The Shape
base class has two derivative shapes: Circle
and Square
. The third derivative is the composite. CompositeShape
keeps a list of many Shape
instances. When called on CompositeShape
, Draw()
delegates that method to all the Shape
instances in the list.
Thus, an instance of CompositeShape
appears to the system to be a single Shape
. It can be passed to any function or object that takes a Shape
, and it will behave like a Shape
. However, it is really a proxy1 for a group of Shape
instances. Listings 31-1 and 31-2 show one possible implementation of CompositeShape
.
public interface Shape
{
void Draw();
}
Listing 31-2. CompositeShape.cs
using System.Collections;
public class CompositeShape : Shape
{
private ArrayList itsShapes = new ArrayList();
public void Add(Shape s)
{
itsShapes.Add(s);
}
public void Draw()
{
foreach (Shape shape in itsShapes)
shape.Draw();
}
}
Consider our discussion of Sensors
and Command
objects in Chapter 21. Figure 21-3 showed a Sensor
class using a Command
class. On detecting its stimulus, the Sensor
called Do()
on the Command
.
What I failed to mention then was that often, a Sensor
had to execute more than one Command
. For example, when it reached a certain point in the paper path, the paper would trip an optical sensor. That sensor then stopped a motor, started another, and engaged a particular clutch.
At first, we took this to mean that every Sensor
class would have to maintain a list of Command
objects (see Figure 31-2). However, we soon recognized that whenever it needed to execute more than one Command
, a Sensor
always treated those Command
objects identically. That is, it simply iterated over the list and called Do()
on each Command
. This was ideal for the COMPOSITE pattern.
So we left the Sensor
class alone and created a CompositeCommand
, as shown in Figure 31-3. This meant that we didn’t have to change the Sensor
or the Command
. We were able to add the plurality of Command
s to a Sensor
without changing either. This is an application of OCP.
This leads to an interesting issue. We were able to make our Sensor
s behave as though they contained many Command
s, without having to modify the Sensor
s. There must be many other situations like this in normal software design. There must be times when you could use COMPOSITE rather than building a list or vector of objects.
In other words, the association between Sensor
and Command
is 1:1. We were tempted to change that association to 1:many. But instead, we found a way to get 1:many behavior without a 1:many relationship. A 1:1 relationship is much easier to understand, code, and maintain than a 1:many relationship is, so this was clearly the right design tradeoff. How many of the 1:many relationships in your current project could be 1:1 if you used COMPOSITE?
Of course, not all 1:many relationship can be reverted to 1:1 by using COMPOSITE. Only those in which every object in the list is treated identically are candidates. For example, if you maintained a list of employees and searched through that list for employees whose paydate is today, you probably shouldn’t use the COMPOSITE pattern, because you wouldn’t be treating all the employees identically.
Quite a few 1:many relationships qualify for conversion to COMPOSITE. The advantages are significant. Instead of duplicating the list management and iteration code in each of the clients, that code appears only once in the composite class.
18.188.61.223