Book cover
All rights reserved. Version for personal use only.
This web version is subjected to minor edits. To report errors or typos, use this form.

Home | Dark Mode | Cite

Software Engineering: A Modern Approach

Marco Tulio Valente

1 Composite Design Pattern

1.1 Introduction

In this article, we will introduce the Composite design pattern, which, due to its simplicity, was not covered in Chapter 6 of the book.

We will use the same structure of the design patterns we studied in that chapter. That is, 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

Assume that we are implementing a graphic editor, similar to Paint (for Windows) or Canva (Web). 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 shape 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 may perform an operation on it, such as changing the background color of all three shapes, using a single method call.

Simple and composite shapes

In other words, clients can work with simple or composite shapes in the same way, using the Shape interface. The client code doesn’t need to know what type of shape (simple or composite) it is manipulating.

1.4 Solution

The Composite design pattern is the solution to the problem we stated. It allows storing composite objects in a tree-like structure and then lets clients handle these objects as if they were simple objects.

The main class of the pattern is responsible for the composite behavior. In our example, we should create 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 a shape from the composite.

  • The class is also a shape, as it implements the Shape interface. The implementation of the Shape methods simply delegates the same operation to all shapes in the composite through a for loop.

Let’s also check out a UML diagram with the classes we’ve created so far:

Classes for implementing the Composite Pattern

As a result, for a client code, it doesn’t matter if it’s working with a simple shape or a composite shape, as in the following case:

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();
    composite.add(c1);
    composite.add(t1);
    composite.add(t2);
    client.foo(composite1); // calls foo with a composite shape
  } 
}

Finally, note that we can create a hierarchy, organized as a tree, with composite shapes containing other composite shapes, as in:

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 (that is, objects formed by grouping simple objects) uniformly. This simplifies the client code that needs to handle such objects, because it remains transparent whether the manipulation occurs on a simple or on 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 and Triangle)
  • The composite class (in our example, CompositeShape)

Considering these classes, think of another example of the Composite pattern. Then answer:

  1. What is the client-visible interface? What methods does it define?
  2. What are the simple object classes? Just list their names.
  3. What is the composite class? Just list its name.