Structural Design Pattern: Composite

The Composite Design Pattern allows objects to be organized in a tree hierarchy, enabling uniform treatment of individual and grouped elements.

Photo by Kelly Sikkema on Unsplash

This article is part of a series exploring design patterns using the Java programming language. The goal of this series is to help readers develop a solid understanding of design patterns while also sharing real-world examples from actual codebases that make use of these patterns.

In this article, we’ll be discussing the Composite pattern.

Symbols for better navigation:

🤔 Hypothetical Scenario/Imagination

⚠️ Warning

👉 Point to note

📝 Common Understanding

Composite Pattern

Composite pattern is used to represent objects in a tree hierarchy.
In a composite pattern, the objects are created as subclasses of the same abstract class template.

A very good example of this is Java’s AWT library, which is used to draw UI elements. The library’s top-level component creates two kinds of composites 👉

  • Container: Holds the UI elements.
  • Leaf: Represents basic UI elements like buttons, text boxes, etc.
Composite UML Diagram of Java AWT library elements

Recipe to cook the Composite Pattern for objects

⚠️ Objects cannot be converted to composites.

Hence, we have to start by building them from scratch.

  1. Create an abstract component class template, which will act as the parent component for all objects.
  2. Create different object types that extend the component class.
  3. Relate objects to each other by composing these components as children of each other.

Let’s create our abstract component class template.

Java
public abstract class Component {
    String name;
    String url;

    List<Component> children = new ArrayList<>();

    public String getName() {
        return name;
    }

    public String getUrl() {
        return url;
    }

    public void add(Component c) {
        throw new UnsupportedOperationException("This operation is not supported");
    }

    public void remove(Component c) {
        throw new UnsupportedOperationException("This operation is not supported");
    }

    public abstract String toString();
}

Now, let’s create our first object based on the component.

Java
public class Parent extends Component {
    public Parent(String name, String url) {
        this.name = name;
        this.url = url;
    }

    @Override
    public void add(Component c) {
        this.children.add(c);
    }

    @Override
    public void remove(Component c) {
        this.children.remove(c);
    }
}

Now, we’ll create one more component as follows.

Java
public class Item extends Component {
    public Item(String name, String url) {
        this.name = name;
        this.url = url;
    }

    @Override
    public String toString() {
        return this.name;
    }
    
    // ... override add/remove methods if required
}

Finally, we can use this as follows.

Java
public class CompositeDesignPatternDemo {
  public static void main() {
    Parent par = new Parent("parent 1", "http://localhost");
    Item item1 = new Item("item 1", "http://localhost/hello");
    Item item2 = new Item("item 2", "http://localhost/world");
    par.add(item1);
    par.add(item2);
  }
}

Case-Study

🤔 Imagine we’re designing a UI library, just like Java’s AWT.

You start by creating basic elements like Button, Label, TextField, etc. Each of these can draw itself on the screen.

Now, we want to group several elements together, say, to create a Container that contains a Label and a Button.

So we create another element to contain these elements.

So far, so good.

⚠️ The challenge begins here:

  • We have to write separate logic for both the container and the individual elements to render on the screen.
  • Some effects, like colour, layout, enable/disable, etc., for the UI elements will remain the same. But how do we write logic in a manner that can apply uniformly to all types of elements?

Without a structured design, you might end up writing separate logic for individual components and for groups.

👉 Here, we can think of using the Composite Design Pattern.

We’ll start by creating the base template UIComponent for all the elements.t for all the elements.

Java
public abstract class UIComponent {
    private List<UIComponent> children;
    
    public void add(UIComponent comp) {
      throw new Exception("method not implemented");
    }
    
    public void remove(UIComponent comp) {
      throw new Exception("method not implemented");
    }
    
    abstract void render();
}

Now, we’ll start with our Container object as follows.

Java
class Container implements UIComponent {
    public void add(UIComponent component) {
        children.add(component);
    }

    public void remove(UIComponent component) {
        children.remove(component);
    }

    @Override
    public void render() {
        System.out.println("Rendering Panel:");
        for (UIComponent child : children) {
            child.render(); // delegate render call to children
        }
    }
}

Finally, let’s create all our basic UI elements like buttons, labels, etc.

Java
// Leaf
class Button implements UIComponent {
    private String label;

    public Button(String label) {
        this.label = label;
    }

    @Override
    public void render() {
        System.out.println("Rendering Button: " + label);
    }
}

// Leaf
class Label implements UIComponent {
    private String text;

    public Label(String text) {
        this.text = text;
    }

    @Override
    public void render() {
        System.out.println("Rendering Label: " + text);
    }
}

With this design, drawing a complex UI becomes very easy! Take a look.

Java
public class CompositeExample {
    public static void main(String[] args) {
        // Create individual components
        Button okButton = new Button("OK");
        Label messageLabel = new Label("Welcome to the app");

        // Create a composite component (Panel)
        Panel mainPanel = new Panel();
        mainPanel.add(messageLabel);
        mainPanel.add(okButton);

        // Client treats everything as a UIComponent
        mainPanel.render();
    }
}

Real-World Use Case of Composite

The File in the Java.io package of Java JDK mimics the file system of the operating system.

A directory can contain files or other directories; both should be treated uniformly.

  • Directory: composite (can contain files and subdirectories)
  • FileSystemNode: abstract component.
  • File: leaf

The File can then be used as a directory to contain more files, as shown in the code example.

https://github.com/openjdk/jdk/blob/eff6439e75d79c67370e79638024296e01101b48/src/java.base/share/classes/java/io/File.java#L1137

Up Next in the Series

Facade Design Pattern

Subscribe to my newsletter today!

Share it on

Leave a Reply

Your email address will not be published. Required fields are marked *