TypeScript Composite Pattern Example

1. Definition

The Composite pattern is a structural design pattern that allows you to treat individual objects and compositions of objects uniformly. It composes objects into tree structures to represent part-whole hierarchies.

2. Problem Statement

When dealing with tree-structured data, clients have to differentiate between leaf nodes and branch (composite) nodes. This complicates client code and increases the difficulty of adding new types of nodes in the future.

3. Solution

The Composite pattern allows you to work with complex tree structures more easily. Both leaf and composite nodes implement a common interface. This uniformity lets you treat individual objects and compositions of objects consistently, simplifying client code.

4. Real-World Use Cases

1. Graphic systems where shapes can be composed of other shapes.

2. Organizational structures where every employee can be either a manager or a simple employee.

3. File system implementations with files and directories.

5. Implementation Steps

1. Define a component interface that will be implemented by both leaf and composite objects.

2. Create leaf objects that perform a specific action.

3. Create composite objects that can contain leafs and other composites. Implement methods to add, remove, and access child components.

4. Clients work through the component interface, allowing for uniform treatment of leafs and composites.

6. Implementation in TypeScript

// Step 1: Define the component interface
interface Component {
    operation(): string;
}
// Step 2: Create leaf objects
class Leaf implements Component {
    operation(): string {
        return 'Leaf';
    }
}
// Step 3: Create composite objects
class Composite implements Component {
    private children: Component[] = [];
    add(child: Component): void {
        this.children.push(child);
    }
    remove(child: Component): void {
        const childIndex = this.children.indexOf(child);
        if (childIndex > -1) {
            this.children.splice(childIndex, 1);
        }
    }
    operation(): string {
        return `Composite: [${this.children.map(child => child.operation()).join(', ')}]`;
    }
}
// Usage:
const simpleLeaf = new Leaf();
const tree = new Composite();
const branch1 = new Composite();
branch1.add(new Leaf());
branch1.add(new Leaf());
const branch2 = new Composite();
branch2.add(new Leaf());
tree.add(branch1);
tree.add(branch2);
console.log(tree.operation());

Output:

Composite: [Composite: [Leaf, Leaf], Composite: [Leaf]]

Explanation:

The Composite pattern allows you to compose objects into tree structures to represent part-whole hierarchies. 

In our TypeScript example, both Leaf and Composite implement the Component interface. This common interface ensures that individual Leaf objects and compositions of objects (Composite) can be treated uniformly. This design makes it easy to build complex tree structures and operate on them without having to differentiate between leaves and branches.

7. When to use?

The Composite pattern is useful when:

1. You need to represent part-whole hierarchies of objects.

2. You want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly.

3. The design should be able to represent new types of components without changing existing code.


Comments