Low Level Design
Composite Pattern — Tree Structures Treated Uniformly
How the Composite pattern lets you compose objects into tree hierarchies where individual items and groups of items share the same interface — eliminating special-case logic.
What is the Composite Pattern?#
The Composite is a structural design pattern that lets you compose objects into tree structures to represent part-whole hierarchies, and treat individual objects and compositions of objects uniformly.
A leaf and a branch both implement the same interface. The client doesn't need to know which one it's dealing with.
You reach for Composite when:
- You need to represent a tree-like hierarchy (file system, UI components, org charts)
- Clients should treat individual objects and groups of objects the same way
- You want to apply recursive operations over nested structures
Real-World Analogy#
Think of your computer's file system:
- A file is a single unit — it has a name and a size.
- A folder contains files and other folders — it also has a name, and its size is the sum of its contents.
Both files and folders support display() and getSize(). You can call getSize() on a folder and it recursively sums all files inside — without the caller needing to distinguish between files and folders.
Class Diagram#
Violation Code — The Problem#
class File {
private String name;
private int size;
File(String name, int size) { this.name = name; this.size = size; }
void display() { System.out.println("File: " + name + " (" + size + "KB)"); }
int getSize() { return size; }
}
class Folder {
private String name;
private List<Object> children = new ArrayList<>(); // ❌ Object — no type safety
void add(Object child) { children.add(child); }
void display() {
System.out.println("Folder: " + name);
for (Object child : children) {
if (child instanceof File) { // ❌ instanceof everywhere
((File) child).display();
} else if (child instanceof Folder) {
((Folder) child).display();
}
}
}
}
Issues:
- No common interface — File and Folder are unrelated, stored as Object
- Manual type checking — instanceof checks required everywhere
- Violates OCP — adding a Shortcut type means updating every place that checks instanceof
- Tight coupling — client code must know the internal structure
- Not recursive — can't uniformly call operations on the whole tree
Enhanced Code — Composite Pattern#
// Component interface — the common contract
public interface FileSystemComponent {
void display(String indent);
int getSize();
}
// Leaf — a single file
public class File implements FileSystemComponent {
private final String name;
private final int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public void display(String indent) {
System.out.println(indent + "📄 " + name + " (" + size + " KB)");
}
@Override
public int getSize() { return size; }
}
// Composite — a folder that can contain files or other folders
public class Folder implements FileSystemComponent {
private final String name;
private final List<FileSystemComponent> children = new ArrayList<>();
public Folder(String name) { this.name = name; }
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void display(String indent) {
System.out.println(indent + "📁 " + name + " (" + getSize() + " KB)");
for (FileSystemComponent child : children) {
child.display(indent + " "); // recursive — no instanceof needed ✅
}
}
@Override
public int getSize() {
return children.stream().mapToInt(FileSystemComponent::getSize).sum();
}
}
// Client — treats files and folders identically
public class Main {
public static void main(String[] args) {
Folder root = new Folder("root");
Folder docs = new Folder("docs");
docs.add(new File("resume.pdf", 120));
docs.add(new File("cover-letter.docx", 45));
Folder src = new Folder("src");
src.add(new File("Main.java", 8));
src.add(new File("Utils.java", 12));
root.add(docs);
root.add(src);
root.add(new File("README.md", 5));
root.display("");
System.out.println("Total size: " + root.getSize() + " KB");
}
}
Output:
📁 root (190 KB)
📁 docs (165 KB)
📄 resume.pdf (120 KB)
📄 cover-letter.docx (45 KB)
📁 src (20 KB)
📄 Main.java (8 KB)
📄 Utils.java (12 KB)
📄 README.md (5 KB)
Total size: 190 KB
Common LLD Problems Using Composite Pattern#
1. File System Hierarchy#
- Components: File, Directory
- Context: A directory contains files and subdirectories, all treated uniformly.
2. UI Component Trees#
- Components: Button, Label, Panel, Window
- Context: Nested UI layouts where containers hold other containers or leaf components.
3. Organisation Hierarchy#
- Components: Employee, Manager, Director
- Context: Managers have subordinates (employees or other managers); traversal calculates total headcount or salary.
4. Menu System#
- Components: MenuItem, SubMenu
- Context: Render hierarchical menus where items may contain nested submenus.
5. HTML / XML DOM Tree#
- Components: HTMLElement, TextNode, CommentNode
- Context: Every node is treated uniformly for traversal, rendering, or search.
6. Graphics Editor (Figma / Illustrator)#
- Components: Line, Circle, Rectangle, Group
- Context: Grouping shapes and applying operations (move, resize, delete) to both individual shapes and groups.
7. Access Control — Permissions#
- Components: Permission, PermissionGroup
- Context: Assign a group of permissions to users or roles; check any permission recursively.
8. Product Bundling in E-commerce#
- Components: Product, Bundle
- Context: A bundle may contain products or other bundles; total price is computed recursively.
Key Design Tip#
Design the common interface (FileSystemComponent) first. The leaf (File) ignores child-management methods; the composite (Folder) implements them. This is sometimes called the "transparency vs safety" trade-off in Composite design.
| References | Links |
|---|---|
| Article Reference | Refactoring Guru — Composite |