Composite Pattern: Embrace the Power of Recursive Structures

Composite Pattern StatementCompose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

The Composite design pattern, introduced by the Gang of Four (GoF), is a structural design pattern that allows you to compose objects into tree-like structures. It enables you to create hierarchies where individual objects and groups of objects are treated uniformly.

The pattern provides a way to represent part-whole hierarchies, where a component can be either an individual object or a composite object consisting of other components. Clients can interact with these structures without needing to distinguish between individual objects and their compositions.

The Composite pattern promotes code reusability and simplifies the client code by providing a consistent interface for accessing both individual objects and compositions. It also enables the addition or removal of objects dynamically, allowing for flexible and scalable systems.

By leveraging the Composite pattern, you can effectively manage complex relationships and structures in your software, making it easier to manipulate and operate on groups of objects as if they were individual entities.

When to use Composite Pattern

The Composite pattern is useful in scenarios where you need to represent part-whole hierarchies or tree-like structures of objects. Here are some situations where you may consider using the Composite pattern:

  1. Object hierarchy: When you have a hierarchy of objects where both individual objects and groups of objects need to be treated uniformly, the Composite pattern can provide a consistent interface for accessing and manipulating them.
  2. Recursive composition: When objects can contain other objects of the same type, and you want to apply operations or access properties uniformly across the entire hierarchy, the Composite pattern simplifies the management of these recursive compositions.
  3. Simplifying client code: If you want to hide the complexity of the underlying object structure from the client code, the Composite pattern can provide a unified interface for interacting with both individual objects and compositions, reducing client code complexity.
  4. Dynamic structure changes: When you need the ability to add or remove objects dynamically to the hierarchy without impacting the client code, the Composite pattern allows for flexible structure modifications at runtime.
  5. Code reusability: If you want to reuse code between individual objects and composite objects, the Composite pattern promotes code reusability by providing a common interface and behavior for both types of objects.

Real World Scenario

  • In many countries, armies are organized in hierarchical structures. An army is composed of multiple divisions, each division consists of several brigades, and a brigade is further comprised of platoons. Platoons can be further divided into squads, which are small groups of actual soldiers. Orders originate at the highest level of the hierarchy and are cascaded down through each level, ensuring that every soldier is aware of their tasks and responsibilities.
  • File Systems: File systems can be represented using the Composite pattern. Directories can contain subdirectories or files, forming a hierarchical structure. Each component (directory or file) within the file system can have common operations like listing, searching, or deleting. The Composite pattern allows the file system to treat directories and files uniformly, simplifying file management and traversal.
  • Organization Hierarchies: In organizational structures, the Composite pattern can be applied to represent hierarchies of employees. Managers can have subordinates (other managers or employees) in a hierarchical manner. This pattern allows consistent operations like reporting lines, access permissions, or calculating total salaries, treating individual employees and hierarchical structures uniformly.
composite pattern - neatcode

Problem

Say you want to represent a company’s org chart. A manager can have another manager or other Individual contributor employees as subordinates. We want to be able to tell at any point the Employees working with a manager.

Implementation

import java.util.ArrayList;
import java.util.List;
// Component
interface Employee {
    void showDetails();
}
// Leaf
class Developer implements Employee {
    private String name;
    private String position;
    public Developer(String name, String position) {
        this.name = name;
        this.position = position;
    }
    @Override
    public void showDetails() {
        System.out.println("Developer: " + name + ", Position: " + position);
    }
}
// Composite
class Manager implements Employee {
    private String name;
    private String position;
    private List<Employee> employees = new ArrayList<>();
    public Manager(String name, String position) {
        this.name = name;
        this.position = position;
    }
    public void addEmployee(Employee employee) {
        employees.add(employee);
    }
    public void removeEmployee(Employee employee) {
        employees.remove(employee);
    }
    @Override
    public void showDetails() {
        System.out.println("Manager: " + name + ", Position: " + position);
        System.out.println("Subordinates:");
        for (Employee employee : employees) {
            employee.showDetails();
        }
    }
}
// Client
public class CompositePatternExample {
    public static void main(String[] args) {
        Employee developer1 = new Developer("John", "Senior Developer");
        Employee developer2 = new Developer("Jane", "Junior Developer");
        Manager manager1 = new Manager("Mike", "Development Manager");
        manager1.addEmployee(developer1);
        manager1.addEmployee(developer2);
        Employee developer3 = new Developer("Mark", "Junior Developer");
        Manager manager2 = new Manager("Mary", "Project Manager");
        manager2.addEmployee(developer3);
        manager2.addEmployee(manager1);
        manager2.showDetails();
    }
}
/** Output:
Manager: Mary, Position: Project Manager
Subordinates:
Developer: Mark, Position: Junior Developer
Manager: Mike, Position: Development Manager
Subordinates:
Developer: John, Position: Senior Developer
Developer: Jane, Position: Junior Developer
*/
Java
Composite pattern class diagram

In this example, we have two types of employees: Developer (leaf) and Manager (composite). The Manager class can contain both individual Developer objects and other Manager objects as its subordinates.

The Employee interface defines the common showDetails() method, which is implemented by both Developer and Manager classes.

The Manager class maintains a list of Employee objects (employees) and provides methods to add or remove subordinates.

In the CompositePatternExample class, we create a hierarchy of employees, with managers having developers as their subordinates. We then call the showDetails() method on the top-level manager, which recursively displays the details of all employees in the hierarchy.

When you run the example, it will output the hierarchical structure and details of the employees.

Composite Pattern & Design Principles

The Composite pattern aligns with several important design principles, including the following:

  1. Single Responsibility Principle (SRP): Each class within the Composite pattern should have a single responsibility. Leaf classes focus on representing individual objects, while the Composite class handles the composition of objects and the traversal of the hierarchy. By adhering to the SRP, the Composite pattern promotes maintainability and allows for easier modifications and extensions.
  2. Open-Closed Principle (OCP): The Composite pattern is designed to be open for extension but closed for modification. You can easily add new types of components or extend the hierarchy without modifying existing code. This adherence to the OCP ensures that changes to the hierarchy structure do not require modifying the client code, enhancing the pattern’s flexibility and modularity.
  3. Liskov Substitution Principle (LSP): The Composite pattern respects the Liskov Substitution Principle by treating both individual objects (Leaf) and compositions of objects (Composite) uniformly. Clients can operate on components at any level of the hierarchy without needing to know their specific types. This principle ensures that subclasses can be substituted for their base classes seamlessly, promoting polymorphism and maintaining correctness in behavior.
  4. Interface Segregation Principle (ISP): The Composite pattern benefits from adhering to the ISP by defining a clear interface or abstraction for components within the hierarchy. This allows clients to interact with objects uniformly without being exposed to irrelevant methods or behaviors. By segregating the interfaces appropriately, the pattern improves cohesion and avoids unnecessary dependencies.
  5. Dependency Inversion Principle (DIP): The Composite pattern can embody the Dependency Inversion Principle by depending on abstractions rather than concrete implementations. This principle promotes loose coupling between components, allowing for easier substitution and decoupling of the composite structure from specific implementations. By relying on abstractions, the Composite pattern enhances flexibility and promotes maintainability.