Composite Pattern (with Example)

Composite patterns allow clients to treat individual objects (under a hierarchical structure) in a uniform manner.

Composite design pattern

Composite patterns allow clients to treat individual objects (under a hierarchical structure) in a uniform manner.

The composite pattern is most suitable for working with objects that form a tree-like hierarchy. In that tree, each node/object (except the root node) is either a composite or leaf node. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.

1. When to use Composite Pattern?

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

  • When the application has a hierarchical structure and needs generic functionality across the structure.
  • When an application needs to aggregate data across a hierarchy.
  • When an application wants to treat composite and individual objects uniformly.

Real-life example usage of composite design patterns may be:

  • Building consolidated view of a customer’s account in the bank (i.e., customer’s portfolio)
  • Building general ledgers
  • Computer/network monitoring applications
  • Retail and inventory applications
  • Directory structure in file system implementations
  • Menu items in GUI screens

2. Design Participants

Below are the participants in any composite pattern-based solution.

Composite design pattern
Composite design pattern

Where the classes and objects participating in this pattern are:

  1. Component
    • declares the interface for objects in the composition.
    • implements default behavior for the interface common to all classes, as appropriate.
    • declares an interface for accessing and managing its child components.
  2. Leaf
    • represents leaf objects in the composition. A leaf has no children.
    • defines behavior for primitive objects in the composition.
  3. Composite
    • defines behavior for components having children.
    • stores child components.
    • implements child-related operations in the Component interface.
  4. Client
    • manipulates objects in the composition through the Component interface.

In the above diagram, the client uses the Component interface to interact with objects in a composite hierarchy. Inside the hierarchy, if an object is composite, it passes the request to leaf nodes. If an object is a leaf node, the request is handled immediately.

Composite leaves also have the choice of modifying the request/response either before or after the leaf node handles it.

3. The Problem

Let’s suppose we are building a financial application. We have customers with multiple bank accounts. We are asked to prepare a design that can help generate the customer’s consolidated account view, which can show the customer’s total account balance and the consolidated account statement after merging all the account statements. So, the application should be able to generate:

  • The customer’s total account balance from all accounts
  • Consolidated account statement

4. Implementation

Here, we are dealing with account objects, which form a tree-like structure in which we will traverse and perform some operations on account objects only. Thus, we can apply the composite design pattern.

Let’s see the participating classes:

Component.java

public abstract class Component {

    AccountStatement accStatement;
    protected List<Component> list = new ArrayList<>();
    public abstract float getBalance();
    public abstract AccountStatement getStatement();
 
    public void add(Component g) {
        list.add(g);
    }
 
    public void remove(Component g) {
        list.remove(g);
    }
 
    public Component getChild(int i) {
        return (Component) list.get(i);
    }
}

CompositeAccount.java

public class CompositeAccount extends Component {

    private float totalBalance;
    private AccountStatement compositeStmt, individualStmt;
 
    public float getBalance() {
        totalBalance = 0;
        for (Component f : list) {
            totalBalance = totalBalance + f.getBalance();
        }
        return totalBalance;
    }
 
    public AccountStatement getStatement() {
        for (Component f : list) {
            individualStmt = f.getStatement();
            compositeStmt.merge(individualStmt);
        }
        return compositeStmt;
    }
}

AccountStatement.java

public class AccountStatement {

    public void merge(AccountStatement g) {
        //Use this function to merge all account statements
    }
}

DepositAccount.java

public class DepositAccount extends Component {

    private String accountNo;
    private float accountBalance;
     
    private AccountStatement currentStmt;
     
    public DepositAccount(String accountNo, float accountBalance) {
        super();
        this.accountNo = accountNo;
        this.accountBalance = accountBalance;
    }
 
    public String getAccountNo() {
        return accountNo;
    }
 
    public float getBalance() {
        return accountBalance;
    }
 
    public AccountStatement getStatement() {
        return currentStmt;
    }
}

SavingsAccount.java

public class SavingsAccount extends Component {

    private String accountNo;
    private float accountBalance;
     
    private AccountStatement currentStmt;
     
    public SavingsAccount(String accountNo, float accountBalance) {
        super();
        this.accountNo = accountNo;
        this.accountBalance = accountBalance;
    }
 
    public String getAccountNo() {
        return accountNo;
    }
 
    public float getBalance() {
        return accountBalance;
    }
 
    public AccountStatement getStatement() {
        return currentStmt;
    }
}

5. Demo

Now, let’s see how the pattern helps in calling the methods transparently without worrying about the object types.

// Creating a component tree
Component component = new CompositeAccount();

// Adding all accounts of a customer to component
component.add(new DepositAccount("DA001", 100));
component.add(new DepositAccount("DA002", 150));
component.add(new SavingsAccount("SA001", 200));

// getting composite balance for the customer
float totalBalance = component.getBalance();
System.out.println("Total Balance : " + totalBalance);
 
AccountStatement mergedStatement = component.getStatement();
//System.out.println("Merged Statement : " + mergedStatement);

6. Final notes

  • The composite pattern defines class hierarchies consisting of individual objects and composite objects.
  • Clients treat primitive and composite objects uniformly through a component interface, which makes client code simple.
  • Adding new components can be easy, and client code does not need to be changed since the client interacts with the new components through the component interface.
  • Composite hierarchy can be traversed with Iterator design pattern.
  • Visitor design patterns can be applied to an operation over a composite.
  • Flyweight design pattern is often combined with Composite to implement shared leaf nodes.

Happy Learning !!

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.