Skip to content

Design Pattern: Composite (Structural)

Treat a tree and a single item the same way

Builds on Interfaces and Class Relationships: Association, Aggregation and Composition

Stage 3core2 min read

The Composite Pattern Solution

The refactored code uses the Composite Pattern. It builds objects into a tree and lets you use the whole tree as if it were one object. The new CartItem interface gives Uniformity: the client does not care whether it holds a single product or a bundle.


1. Key Improvements

Uniformity and Polymorphism

  • The Benefit: Both Product (Leaf) and ProductBundle (Composite) implement the same CartItem interface.
  • The Impact: The client has no instanceof checks or casts. It calls getPrice() or display() on a CartItem.

Recursive Hierarchy (Nesting)

  • The Benefit: ProductBundle now holds a list of CartItem, not just Product.
  • The Impact: A bundle can now contain other bundles, so trees can go deep (e.g., a "Mega Combo" containing an "iPhone Bundle" and a "Case Bundle").

Type Safety

  • The Benefit: The cart is now a List<CartItem> instead of a List<Object>.
  • The Impact: The compiler now checks the types, so a ClassCastException cannot happen here.

Open/Closed Principle (OCP)

  • The Benefit: You can add new item types without changing old code.
  • The Impact: To add a new type such as DigitalService or GiftCard, implement CartItem. Main and ProductBundle stay unchanged.

2. Structural Breakdown

ComponentResponsibility
Component (CartItem)The interface with the operations that both simple and complex elements share.
Leaf (Product)A basic element with no sub-elements.
Composite (ProductBundle)An element that holds sub-elements (leaves or other composites). It passes the work to its children.
Client (Main)Uses all elements only through CartItem.

3. Comparison of "Before" vs "After"

FeatureBefore (Manual Type Checking)After (Composite Pattern)
Client LogicComplex; uses instanceof and casting.Simple; calls interface methods polymorphically.
Nesting CapabilitiesFlat; bundles only hold products.Recursive; bundles can hold bundles.
Type SafetyLow; uses List<Object>.High; uses List<CartItem>.
AbstractionLeaky; client knows internal types.Hidden; client only knows the interface.

4. Summary of the "Tree"

The Composite Pattern turns your data into a Tree Structure. Ask the "root" (the Cart) for the total price, and it asks its children. A "Leaf" returns its own price. A "Composite" asks its own children. This recursive delegation (a method calling the same method on its children) goes on until the whole tree is done.

In this topic

After code1 file

After/Main.java116 linesGitHub
package DesignPatterns.StructuralPatterns.CompositeDesignPattern.After;import java.util.*;/** * 1. THE COMPONENT INTERFACE * This is the heart of the pattern. It defines the common operations for * both simple items (Leaf) and groups of items (Composite). */interface CartItem {  double getPrice();  void display(String indent);}/** * 2. THE LEAF * Represents individual products. It implements the interface methods * directly without knowing about any other items. */class Product implements CartItem {  private String name;  private double price;  public Product(String name, double price) {    this.name = name;    this.price = price;  }  @Override  public double getPrice() {    return price;  }  @Override  public void display(String indent) {    System.out.println(indent + "Product: " + name + " - ₹" + price);  }}/** * 3. THE COMPOSITE * Represents a bundle or collection. * Key Concept: It contains a list of 'CartItem' (the interface), not 'Product'. * This allows it to hold both Products AND other ProductBundles. */class ProductBundle implements CartItem {  private String bundleName;  // Uniformity: Holds any CartItem (Leaf or another Composite)  private List<CartItem> items = new ArrayList<>();  public ProductBundle(String bundleName) {    this.bundleName = bundleName;  }  public void addItem(CartItem item) {    items.add(item);  }  @Override  public double getPrice() {    double total = 0;    // Delegation: The bundle doesn't calculate its own price;    // it asks each child item for its price and sums them up.    for (CartItem item : items) {      total += item.getPrice();    }    return total;  }  @Override  public void display(String indent) {    System.out.println(indent + "Bundle: " + bundleName);    for (CartItem item : items) {      // Recursive call: Works regardless of how deep the tree goes      item.display(indent + "  ");    }  }}/** * 4. THE CLIENT * Uses polymorphism to interact with items. */class Main {  public static void main(String[] args) {    // Treat everything as a 'CartItem'    CartItem book = new Product("Atomic Habits", 499);    // Create a bundle and add items    ProductBundle iphoneCombo = new ProductBundle("iPhone Essentials Combo");    iphoneCombo.addItem(new Product("iPhone 15", 79999));    iphoneCombo.addItem(new Product("AirPods", 15999));    // TYPE SAFETY: No more List<Object>.    // We use the common interface type.    List<CartItem> cart = new ArrayList<>();    cart.add(book);    cart.add(iphoneCombo);    System.out.println("Your Amazon Cart:");    double total = 0;    // NO INSTANCEOF: Polymorphism handles the call correctly.    // Whether it's a Product or a Bundle, we just call display() and getPrice().    for (CartItem item : cart) {      item.display("  ");      total += item.getPrice();    }    System.out.println("\nTotal: ₹" + total);  }}

Done with this topic?

How well do you know it?

PreviousDesign Pattern: Facade (Structural)