Structural Patterns  «Prev  Next»

The Composite Pattern: Role and Function in Structural Patterns

  1. Introduction: In the lexicon of software design, structural patterns provide foundational methodologies to compose classes or objects into intricate and scalable structures, ensuring the cohesion and sustainability of the system. Within this framework, the Composite Pattern holds a pivotal position, delineating a mechanism to treat individual objects and compositions of objects uniformly. This pattern's crux lies in its ability to transform intricate structures into simpler recursive formations.
  2. Role of the Composite Pattern:
    The Composite Pattern's raison d'être is to facilitate the composition of objects into tree-like structures, representing part-whole hierarchies. Its primary role is to ensure that individual objects (leaves) and compositions of objects (composites) are treated indistinguishably.
    2.1. Primary Roles:
    1. Simplifier: The Composite Pattern abstracts and simplifies the client's interaction with both single and composite elements, ensuring uniformity in object treatment.
    2. Hierarchical Structurer: It provides a framework to establish part-whole hierarchies, allowing for the creation of complex structures while maintaining clarity.
  3. Function of the Composite Pattern:
    The Composite Pattern encapsulates objects into tree structures, enabling clients to interact with both individual elements and their compositions uniformly.
    3.1. Key Components:
    1. Component: An abstract class or interface which defines a common interface for all concrete objects (both atomic and composite). It may also implement default behaviors and manage child components.
    2. Leaf: Denotes the individual objects, devoid of any child. It implements the component interface and represents the end objects in the composition.
    3. Composite: A concrete class which implements the component interface and holds children, which can either be other Composites or Leafs. It manages child components and forwards requests to them, allowing recursive composition.
    3.2. Workflow:
    1. Clients interact with the Component interface to treat both Leaf and Composite objects uniformly.
    2. When operations are invoked on a Composite, it can delegate these operations to its children, possibly performing additional operations before or after the delegation.
  4. Implications of the Composite Pattern:
    1. Transparency vs. Safety: While the pattern promotes transparency by treating Leaf and Composite objects uniformly, this might lead to safety issues. For instance, certain operations might be meaningful only for Composite and not for Leaf.
    2. Ease of Manipulation: The Composite Pattern allows clients to seamlessly add, remove, or modify components in the hierarchy without needing to identify the object's type.
  5. Conclusion: The Composite Pattern stands as a beacon in the realm of structural patterns, advocating for the amalgamation of individual and composite objects in a manner that obscures the distinction between them. Through its recursive nature and ability to unify disparate components under a singular interface, the pattern illuminates a pathway for developers to craft systems that are both intricate in their architecture yet straightforward in their interactions. It encapsulates the very essence of structural integrity, ensuring that complex systems retain an element of simplicity and elegance in their design. In academic discourse, the Composite Pattern is celebrated as a paragon of design ingenuity, bridging the gap between complexity and uniformity.
The Composite pattern enables you to create hierarchical tree structures of varying complexity, while allowing every element in the structure to operate with a uniform interface. The Composite pattern combines objects into tree structures to represent either the whole hierarchy or a part of the hierarchy. This means the Composite pattern allows clients to treat individual objects and compositions of objects uniformly. The figure below illustrates the Composite pattern.
Furthermore, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects are to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.

Motivation for the Composite Pattern

When dealing with Tree-structured data, programmers often have to discriminate between a leaf-node and a branch. The solution is an interface that allows treating complex and primitive objects uniformly. In object-oriented programming, a composite is an object designed as a composition of one-or-more similar objects, all exhibiting similar functionality.
This is known as a "has-a" relationship between objects. The key concept is that you can manipulate a single instance of the object just as you would manipulate a group of them. The operations you can perform on all the composite objects often have a least common denominator relationship. For example, if defining a system to portray grouped shapes on a screen, it would be useful to define resizing a group of shapes to have the same effect (in some sense) as resizing a single shape.

Diagram of the Composite Pattern

Abstract Component class with subclasses 1) Leaf and 2) Composite
The diagram represents UML class diagram illustrating the "Composite Design Pattern", one of the Gang of Four (GoF) Structural Patterns.
Here's a breakdown of its components:
Purpose: The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
Class Diagram Breakdown:
  1. Component (Abstract Class)
    • This is the base class/interface for all objects in the composition.
    • Marked as <<abstract>> Component
    • Methods:
      • operation(): void — common operation for both leaf and composite.
      • add(c: Component): void — adds a child (only implemented in Composite).
      • remove(c: Component): void — removes a child (only implemented in Composite).
      • getChild(i: int): Component — retrieves a child (only for Composite).
  2. Leaf (Concrete Class)
    • Inherits from Component.
    • Represents leaf objects in the composition — no children.
    • Implements operation(), but not add, remove, or getChild.
  3. Composite (Concrete Class)
    • Inherits from Component.
    • Can contain children (other Components).
    • Implements all operations: operation(), add(), remove(), and getChild().
  4. Composite Client
    • Interacts with the Component interface.
    • This client can treat Leaf and Composite objects uniformly via the Component interface.

Relationships:
  1. Inheritance:
    • Both Leaf and Composite inherit from Component (denoted by solid lines with hollow arrows pointing to Component).
  2. Composition/Aggregation:
    • Composite has a diamond association labeled child pointing to Component, indicating that Composite contains Component children (part-whole relationship).
  3. Client Association:
    • Composite Client connects to Component, signifying it uses the abstract interface without knowing if it deals with a Leaf or a Composite.

Summary: This diagram exemplifies how the Composite Pattern enables **uniform treatment of individual objects and compositions** of objects. It promotes flexibility and simplicity when building hierarchical tree structures. Abstract Component class with subclasses 1) Leaf and 2) Composite

✅ Java Code Implementation – Composite Pattern

Here is a Java implementation of the Composite Design Pattern based on the class diagram provided above:
import java.util.ArrayList;
import java.util.List;

// Abstract Component
abstract class Component {
    protected String name;

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

    public abstract void operation();

    // Default implementations (overridden in Composite)
    public void add(Component c) {
        throw new UnsupportedOperationException("add() not supported");
    }

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

    public Component getChild(int i) {
        throw new UnsupportedOperationException("getChild() not supported");
    }
}

// Leaf class
class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    public void operation() {
        System.out.println("Leaf: " + name + " operation performed.");
    }
}

// Composite class
class Composite extends Component {
    private List<Component> children = new ArrayList<>();

    public Composite(String name) {
        super(name);
    }

    @Override
    public void operation() {
        System.out.println("Composite: " + name + " operation started.");
        for (Component child : children) {
            child.operation();
        }
    }

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

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

    @Override
    public Component getChild(int i) {
        return children.get(i);
    }
}

// Client class
public class CompositeClient {
    public static void main(String[] args) {
        Component leaf1 = new Leaf("Leaf A");
        Component leaf2 = new Leaf("Leaf B");

        Composite composite1 = new Composite("Composite X");
        composite1.add(leaf1);
        composite1.add(leaf2);

        Component leaf3 = new Leaf("Leaf C");
        Composite composite2 = new Composite("Composite Y");
        composite2.add(leaf3);
        composite2.add(composite1);

        // The client treats all components uniformly
        composite2.operation();
    }
}

📌 Output
Composite: Composite Y operation started.
Leaf: Leaf C operation performed.
Composite: Composite X operation started.
Leaf: Leaf A operation performed.
Leaf: Leaf B operation performed.

Benefits of the Composite Pattern:

The following lists the benefits of using the Composite pattern:
  1. Defines class hierarchies consisting of primitive objects and composite objects
  2. Makes it easier to add new kinds of components
  3. Provides flexibility of structure and a manageable interface

When to use Composite Pattern:

You should use the Composite pattern when:
  1. You want to represent the whole hierarchy or part of the hierarchy of objects.
  2. You want clients to be able to ignore the difference between compositions of objects and individual objects.
  3. The structure can have any level of complexity, and is dynamic.

Composite Pattern in Java