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 Type Checking Bottleneck

Here a single item and a group of items are unrelated classes. So the code is hard to change and not type-safe. It also cannot hold nested groups, which are common in e-commerce and file systems.


1. Working of the Code

  • The Product: A single item with a name and a price.
  • The Bundle: A group that holds a list of Product objects only.
  • Manual Setup: Items and bundles are created and added to a List<Object> cart, a list that accepts anything.
  • Type Discovery: At checkout, the code asks each item for its type with instanceof.
  • Manual Calculation: Then it casts the item to that type (tells Java what it really is) so it can call the display and price methods.

2. Key Issues in this Design

Violation of Polymorphism (instanceof Abuse)

  • The Issue: The checkout loop uses instanceof to tell items from bundles.
  • The Impact: This breaks polymorphism. With polymorphism, the client calls a method on a shared interface and does not need to know the exact class.

Lack of Uniformity

  • The Issue: Product and ProductBundle are separate types. They share no interface or superclass.
  • The Impact: You cannot write one piece of logic for "anything in the cart". Each type needs its own logic.

Unsafe Abstraction (List<Object>)

  • The Issue: The cart uses List<Object>. This is a "code smell" (a sign of weak design) in Java.
  • The Impact: The compiler cannot check what is in the list. You must cast, and a wrong cast fails at runtime with a ClassCastException.

Non-Recursive Structure

  • The Issue: A ProductBundle can only contain Product objects.
  • The Impact: You cannot put a bundle inside another bundle (for example, a "Holiday Gift Box" holding a "Skincare Kit"). Real hierarchies need this.

Duplicated Logic

  • The Issue: Display and price logic is repeated in both classes.
  • The Impact: If the price rule changes (for example, adding tax), you must update several places. That takes more work and risks bugs.

3. Structural Analysis

ComponentStatusObservation
Individual ItemIsolatedOperates independently with no relation to bundles.
BundleRigidLimited to holding only basic products; cannot nest.
ClientOver-informedMust know the internal types of everything in the cart.

4. Summary of the "Smell"

If you use instanceof to walk through a list, or you cannot nest groups inside groups, you need the Composite Pattern. It lets us treat "parts" and "wholes" the same way.

In this topic

Before code1 file

Before/Main.java101 linesGitHub
package DesignPatterns.StructuralPatterns.CompositeDesignPattern.Before;import java.util.*;/** * PROBLEM 1: Lack of a Common Interface. * There is no 'CatalogComponent' or 'Component' interface. * This prevents treating single items and bundles uniformly. */class Product {    private String name;    private double price;    public Product(String name, double price) {        this.name = name;        this.price = price;    }    public double getPrice() {        return price;    }    public void display(String indent) {        System.out.println(indent + "Product: " + name + " - ₹" + price);    }}/** * PROBLEM 2: Rigid Composition. * This class can only store 'Product' objects. It cannot store other bundles. */class ProductBundle {    private String bundleName;    private List<Product> products = new ArrayList<>();    public ProductBundle(String bundleName) {        this.bundleName = bundleName;    }    // This method is locked to the concrete 'Product' class.    public void addProduct(Product product) {        products.add(product);    }    public double getPrice() {        double total = 0;        for (Product product : products) {            total += product.getPrice();        }        return total;    }    public void display(String indent) {        System.out.println(indent + "Bundle: " + bundleName);        for (Product product : products) {            product.display(indent + "  ");        }    }}class Main {    public static void main(String[] args) {        Product book = new Product("Book", 500);        Product charger = new Product("Charger", 800);        ProductBundle iphoneCombo = new ProductBundle("iPhone Combo Pack");        iphoneCombo.addProduct(charger);        /**         * PROBLEM 3: Type-Safety & Code Smells.         * We have to use List<Object> because there is no common type.         */        List<Object> cart = new ArrayList<>();        cart.add(book);        cart.add(iphoneCombo);        double total = 0;        System.out.println("Cart Details:\n");        /**         * PROBLEM 4: Violation of Open/Closed Principle.         * If we add a new type (e.g., 'DigitalSubscription'), we must         * come back here and add another 'instanceof' block.         */        for (Object item : cart) {            if (item instanceof Product) {                ((Product) item).display("  ");                total += ((Product) item).getPrice();            } else if (item instanceof ProductBundle) {                ((ProductBundle) item).display("  ");                total += ((ProductBundle) item).getPrice();            }        }        System.out.println("\nTotal Price: ₹" + total);    }}

Done with this topic?

How well do you know it?

PreviousDesign Pattern: Facade (Structural)