Implementing Part-Whole Hierarchies with the Composite Design Pattern

Overview of the Composite Strategy

This structural design pattern facilitates the construction of hierarchical trees representing part-whole relationships. Its primary goal is to allow client applications to process both single elements and groups of elements through a unified interface. By defining a common abstract type, the system treats leaf nodes (individual items) and branch nodes (collections) consistent, simplifying recursive operations.

Structural Implementation

To achieve this architecture, we define an abstract base class that declares standard operations for all node types. Concrete classes then inherit this interface, implementing specific behaviors for either containers or leaves.

1. Abstract Component Definition

The foundation of the hierarchy is the AbstractNode class. It establishes the contract to manipulation, including adding subordinates, retrieving children, and rendering output. Default implementations raise exceptions for leaf nodes, enforcing stricter overrides in container classes.

package org.sample.composite.struct;

public abstract class AbstractNode {
    private String label;

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

    public void attach(AbstractNode child) {
        throw new UnsupportedOperationException("Leaf nodes cannot have children");
    }

    public void detach(AbstractNode child) {
        throw new UnsupportedOperationException("Leaf nodes do not manage children");
    }

    public AbstractNode getChild(int index) {
        throw new UnsupportedOperationException("Leaf nodes do not expose children");
    }

    public String getLabel() {
        return label;
    }

    public String getDetails() {
        return null;
    }

    public double getValue() {
        return 0.0;
    }

    public boolean isVeggieFriendly() {
        return false;
    }

    public void display() {
        throw new UnsupportedOperationException("Implementation required in subclass");
    }
}

2. Composite Container Class

The GroupNode acts as a parent capable of storing multiple descendants. It utilizes a list to manage references and overrides methods to enable traversal and modification of the subtree.

package org.sample.composite.struct;

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

public class GroupNode extends AbstractNode {
    private final List<AbstractNode> children = new ArrayList<>();
    private final String summary;

    public GroupNode(String label, String summary) {
        super(label);
        this.summary = summary;
    }

    @Override
    public void attach(AbstractNode child) {
        children.add(child);
    }

    @Override
    public void detach(AbstractNode child) {
        children.remove(child);
    }

    @Override
    public AbstractNode getChild(int index) {
        if (index < 0 || index >= children.size()) {
            return null;
        }
        return children.get(index);
    }

    public String getSummary() {
        return summary;
    }

    @Override
    public void display() {
        System.out.println("▸ " + getLabel());
        System.out.println("   (" + getSummary() + ")");
        System.out.println("-----------------------------------");
        
        // Iterating recursively through child nodes
        children.forEach(AbstractNode::display);
    }
}

3. Leaf Node Implementation

The TextNode represents the terminal element within the structure. It holds specific data attributes like price and deitary information but does not contain other nodes.

package org.sample.composite.struct;

public class TextNode extends AbstractNode {
    private String details;
    private boolean veggie;
    private double cost;

    public TextNode(String label, String details, boolean veggie, double cost) {
        super(label);
        this.details = details;
        this.veggie = veggie;
        this.cost = cost;
    }

    public String getDetails() {
        return details;
    }

    @Override
    public double getValue() {
        return cost;
    }

    @Override
    public boolean isVeggieFriendly() {
        return veggie;
    }

    @Override
    public void display() {
        StringBuilder line = new StringBuilder();
        line.append("   ").append(getLabel());
        if (isVeggieFriendly()) {
            line.append(" (Vegetarian)");
        }
        line.append(" - $").append(String.format("%.2f", getValue()));
        System.out.println(line.toString());
        System.out.println("      Description: " + getDetails());
    }
}

Client Interaction and Testing

The external controller interacts solely with the abstract type. This decouples the client logic from the concrete class specifics, allowing dynamic restructuring of the tree without modifying processing code.

4. Control Handler

package org.sample.composite.struct;

public class OrderController {
    private final AbstractNode rootStructure;

    public OrderController(AbstractNode rootStructure) {
        this.rootStructure = rootStructure;
    }

    public void renderSelection() {
        System.out.println("Loading Complete Catalogue...");
        rootStructure.display();
        System.out.println("Catalogue Finished.");
    }
}

5. Execution Driver

The following setup demonstrates creating a multi-level hierarchy, populating it with data, and invoking the unified display method.

package org.sample.composite.struct;

public class MainRunner {
    public static void main(String[] args) {
        // Define sub-groups
        AbstractNode breakfast = new GroupNode("Morning Meals", "Start your day");
        AbstractNode lunch = new GroupNode("Midday Options", "Fast service");
        AbstractNode drinks = new GroupNode("Beverages", "Refreshments");
        
        // Define the total collection
        AbstractNode fullCollection = new GroupNode("Full Restaurant Menu", "All Departments");

        // Add items to Breakfast group
        breakfast.attach(new TextNode("Omelette", "Eggs with cheese", true, 4.50));
        breakfast.attach(new TextNode("Hash Browns", "Fried potato sticks", false, 2.00));

        // Add items to Lunch group
        lunch.attach(new TextNode("Club Sandwich", "Three-layered meat sandwich", false, 8.00));
        
        // Nesting structure: Drinks as a child of Beverages category
        drinks.attach(new TextNode("Latte", "Coffee with steamed milk", false, 3.50));
        lunch.attach(drinks);

        // Assemble the top level
        fullCollection.attach(breakfast);
        fullCollection.attach(lunch);

        // Process via client
        OrderController server = new OrderController(fullCollection);
        server.renderSelection();
    }
}

Tags: java composite-pattern object-oriented-design software-architecture hierarchy-management

Posted on Mon, 17 Aug 2026 16:18:56 +0000 by cspgsl