Back to Overview

Composite Pattern

The Composite Pattern lets you compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

This pattern is especially useful when you need to work with tree-like object structures, and when you want clients to be able to ignore the differences between composite objects (branches) and individual objects (leaves).

Problem

Imagine you're building a file system where you have files and directories. Both files and directories need to be treated as file system items. Files are simple, but directories can contain other directories and files. How do you design the system so that clients can work with both simple and complex elements through the same interface?

Component Leaf Composite children Root Dir File File File File

Components

Implementation Example

Let's implement a file system with files and directories using the Composite pattern:

// Component - The common interface
class FileSystemItem {
    constructor(name) {
        this.name = name;
    }
    
    // Common methods that all file system items should implement
    getName() {
        return this.name;
    }
    
    getSize() {
        // To be implemented by subclasses
    }
    
    print(indent = 0) {
        // To be implemented by subclasses
    }
}

// Leaf - File class
class File extends FileSystemItem {
    constructor(name, size) {
        super(name);
        this.size = size; // Size in KB
    }
    
    getSize() {
        return this.size;
    }
    
    print(indent = 0) {
        console.log(`${' '.repeat(indent)}📄 ${this.name} (${this.size}KB)`);
    }
}

// Composite - Directory class
class Directory extends FileSystemItem {
    constructor(name) {
        super(name);
        this.children = [];
    }
    
    add(item) {
        this.children.push(item);
        return this;
    }
    
    remove(item) {
        const index = this.children.indexOf(item);
        if (index !== -1) {
            this.children.splice(index, 1);
        }
    }
    
    getSize() {
        // Calculate total size by summing the size of all children
        return this.children.reduce((sum, child) => sum + child.getSize(), 0);
    }
    
    print(indent = 0) {
        console.log(`${' '.repeat(indent)}📁 ${this.name} (${this.getSize()}KB)`);
        
        // Print all children with increased indentation
        this.children.forEach(child => {
            child.print(indent + 2);
        });
    }
}

With this pattern, both Files and Directories share the same interface (FileSystemItem), but directories can also contain other items. Clients can work with both individual files and complex directory structures through the same interface.

Interactive Demo: File System Explorer

Explore a file system structure built with the Composite pattern. You can expand/collapse folders, calculate sizes, and add new items.

// Select an item to see details

When to Use

Benefits

Real-World Uses