Software Engineering: A Modern Approach
1 Composite Design Pattern
1.1 Introduction
In this article, we will introduce the Composite design pattern, which, despite its importance, was not covered in Chapter 6 of the book.
We will use the same structure as the design patterns we explored in that chapter. Specifically, we’ll present the pattern by first describing a context, then a problem faced in this context, and finally, the solution proposed by the Composite design pattern.
1.2 Context
Let’s consider that we are implementing a graphic editor, similar to Paint (for Windows) or Canva (web-based). In this editor, we have interfaces and classes like the following:
interface Shape {
void draw();
void changeBackgroundColor(Color color);
}
class Circle implements Shape {
...
}
class Triangle implements Shape {
...
}
1.3 Problem
In our editor, we want to implement a feature that allows grouping shapes and then treating the resulting group as a single element.
For example, a composite shape may consist of a circle and two triangles (see the illustration). Once this grouping is created, we should be able to perform an operation on it, such as changing the background color of all three shapes, using a single method call.
This approach enables clients to work with simple or composite shapes
in the same way, using the Shape
interface. The client code
doesn’t need to know whether it is manipulating a simple or composite
shape.
1.4 Solution
The Composite design pattern is the solution to the problem we described. It allows us to organize composite objects in a tree-like structure and then lets clients handle these objects as if they were individual objects.
The main class of the pattern is responsible for the composite behavior. In our example, we implement a class like the following:
class CompositeShape implements Shape {
private ArrayList<Shape> shapes = new ArrayList<Shape>();
// list that stores the grouped shapes
public void add(Shape shape) {
shapes.add(shape);
}
public void remove(Shape shape) {
shapes.remove(shape);
}
public void draw() {
for (Shape shape: shapes) {
shape.draw();
}
}
public void changeBackgroundColor(Color color) {
for (Shape shape: shapes) {
shape.changeBackgroundColor(color);
}
}
}
Two main aspects about CompositeShape
should be
highlighted:
The class includes methods to add and remove shapes from the composite.
The class itself is a shape, as it implements the
Shape
interface. The implementation of theShape
methods delegates the operations to all shapes in the composite through afor
loop.
Let’s also examine a UML diagram with the classes we’ve created so far:
As a result, client code doesn’t need to distinguish between a simple shape and a composite shape, as in the following example:
class Client {
void foo(Shape shape) {
...
shape.draw(); // draws simple or composite shapes
...
}
}
class Main {
public static void main(String[] args) {
Client client = new Client();
Circle c1 = new Circle();
client.foo(c1); // calls foo with a simple shape
Triangle t1 = new Triangle();
Triangle t2 = new Triangle();
CompositeShape composite1 = new CompositeShape();
composite1.add(c1);
composite1.add(t1);
composite1.add(t2);
client.foo(composite1); // calls foo with a composite shape
}
}
Finally, note that we can create a hierarchical structure, organized as a tree, with composite shapes containing other composite shapes, as illustrated in this example:
CompositeShape composite2 = new CompositeShape();
composite2.add(composite1); // composite shape containing another composite shape
1.5 Conclusion
We should use the Composite pattern when we need to represent both simple and composite objects (groups of simple objects) uniformly. This approach simplifies client code that handles such objects, as it remains unaware of whether it is manipulating a simple or a composite object.
Exercises
1. There are three types of classes or interfaces in the Composite pattern:
- A client-visible interface (in our example,
Shape
) - Simple object classes (in our example,
Circle
andTriangle
) - The composite class (in our example,
CompositeShape
)
Consider another example of the Composite pattern. Then answer:
- What is the client-visible interface? What methods does it define?
- What are the simple object classes? List their names.
- What is the composite class? List its name.
Check out the other articles on our site.