Composite Pattern

Introduction

The Composite Pattern is a structural design pattern that lets you compose objects into tree structures and then work with those structures as if they were individual objects. Think of a file system — a directory can contain files and other directories. Whether you ask for the size of a single file or an entire directory tree, you use the same operation. The composite handles the recursion for you.

This pattern is essential whenever you deal with hierarchical or tree-like structures where individual items and groups of items should be treated uniformly.

The Problem

You are building a file system utility that calculates the total size of files and directories. A file has a fixed size, but a directory contains other files and subdirectories, each of which may contain more files and subdirectories. You need to calculate the total size at any level of the tree.

Without a unified interface, client code must constantly check whether it is dealing with a file or a directory, leading to scattered conditional logic and code that breaks every time a new type of node is added.

The Solution

Define a common interface (FileSystemComponent) with a getSize() method. Files implement it by returning their own size. Directories implement it by summing the sizes of all their children — which can be files or other directories. Client code simply calls getSize() on any component without caring about its type.

The recursive structure is handled naturally by the composite, and the client code remains clean and simple.

Key Principle

The Composite Pattern follows the Uniform Treatment Principle — clients should not need to distinguish between individual objects and compositions of objects. It also leverages recursive composition, letting you build complex structures from simple, uniform building blocks.

Java Example

Here we model a file system where files and directories share a common interface.

import java.util.ArrayList;
import java.util.List;

// Component interface
public interface FileSystemComponent {
    String getName();
    long getSize();
    void display(String indent);
}

// Leaf: individual file
public class FileItem implements FileSystemComponent {
    private final String name;
    private final long size;

    public FileItem(String name, long size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() { return name; }

    @Override
    public long getSize() { return size; }

    @Override
    public void display(String indent) {
        System.out.printf("%s📄 %s (%d bytes)%n", indent, name, size);
    }
}

// Composite: directory containing files and subdirectories
public class Directory implements FileSystemComponent {
    private final String name;
    private final List<FileSystemComponent> children = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    public Directory add(FileSystemComponent component) {
        children.add(component);
        return this;  // Fluent API
    }

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

    @Override
    public String getName() { return name; }

    @Override
    public long getSize() {
        // Recursively sums sizes of all children
        return children.stream()
                       .mapToLong(FileSystemComponent::getSize)
                       .sum();
    }

    @Override
    public void display(String indent) {
        System.out.printf("%s📁 %s/ (%d bytes total)%n", indent, name, getSize());
        for (FileSystemComponent child : children) {
            child.display(indent + "  ");
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Build a file system tree
        Directory root = new Directory("project");

        Directory src = new Directory("src");
        src.add(new FileItem("Main.java", 2400));
        src.add(new FileItem("Utils.java", 1200));

        Directory config = new Directory("config");
        config.add(new FileItem("application.yml", 350));
        config.add(new FileItem("logback.xml", 500));
        src.add(config);

        Directory test = new Directory("test");
        test.add(new FileItem("MainTest.java", 1800));

        root.add(src);
        root.add(test);
        root.add(new FileItem("README.md", 600));
        root.add(new FileItem("pom.xml", 3200));

        // Uniform interface - works the same for files and directories
        root.display("");
        System.out.println("\nTotal project size: " + root.getSize() + " bytes");

        // Works on any subtree too
        System.out.println("Source directory size: " + src.getSize() + " bytes");
    }
}

Python Example

The same file system composite scenario in Python.

from abc import ABC, abstractmethod


# Component interface
class FileSystemComponent(ABC):

    @abstractmethod
    def get_name(self) -> str:
        pass

    @abstractmethod
    def get_size(self) -> int:
        pass

    @abstractmethod
    def display(self, indent: str = "") -> None:
        pass


# Leaf: individual file
class FileItem(FileSystemComponent):

    def __init__(self, name: str, size: int):
        self._name = name
        self._size = size

    def get_name(self) -> str:
        return self._name

    def get_size(self) -> int:
        return self._size

    def display(self, indent: str = "") -> None:
        print(f"{indent}file: {self._name} ({self._size} bytes)")


# Composite: directory containing files and subdirectories
class Directory(FileSystemComponent):

    def __init__(self, name: str):
        self._name = name
        self._children: list[FileSystemComponent] = []

    def add(self, component: FileSystemComponent) -> "Directory":
        self._children.append(component)
        return self  # Fluent API

    def remove(self, component: FileSystemComponent) -> None:
        self._children.remove(component)

    def get_name(self) -> str:
        return self._name

    def get_size(self) -> int:
        # Recursively sums sizes of all children
        return sum(child.get_size() for child in self._children)

    def display(self, indent: str = "") -> None:
        print(f"{indent}dir:  {self._name}/ ({self.get_size()} bytes total)")
        for child in self._children:
            child.display(indent + "  ")


# Usage
if __name__ == "__main__":
    # Build a file system tree
    root = Directory("project")

    src = Directory("src")
    src.add(FileItem("Main.java", 2400))
    src.add(FileItem("Utils.java", 1200))

    config = Directory("config")
    config.add(FileItem("application.yml", 350))
    config.add(FileItem("logback.xml", 500))
    src.add(config)

    test = Directory("test")
    test.add(FileItem("MainTest.java", 1800))

    root.add(src)
    root.add(test)
    root.add(FileItem("README.md", 600))
    root.add(FileItem("pom.xml", 3200))

    # Uniform interface - works the same for files and directories
    root.display()
    print(f"\nTotal project size: {root.get_size()} bytes")

    # Works on any subtree too
    print(f"Source directory size: {src.get_size()} bytes")

When to Use

  • Tree structures — When your data naturally forms a tree (file systems, org charts, UI component trees, menu systems)
  • Uniform operations — When you need to perform the same operation on individual items and groups of items
  • Recursive aggregation — When you need to calculate aggregate values (size, cost, count) across a hierarchy
  • Dynamic composition — When items and groups need to be assembled and modified at runtime

Real-World Usage

  • Java Swing/AWTComponent and Container form a composite tree where containers hold other components
  • React/DOM — The Virtual DOM is a composite tree where elements can contain other elements
  • Java’s java.io.File — Represents both files and directories with shared operations like length() and listFiles()
  • Python’s ast module — Abstract Syntax Trees where nodes contain other nodes uniformly
  • Spring SecurityGrantedAuthority hierarchies where roles can contain other roles



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

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