StructuralTypeScriptverifiedVerified
Composite Pattern in TypeScript
Composes objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions uniformly.
How to Implement the Composite Pattern in TypeScript
1Step 1: Define the component interface for uniform treatment
interface Component {
operation(): string;
add?(c: Component): void;
remove?(c: Component): void;
isComposite(): boolean;
}2Step 2: Implement Leaf and Composite node types
class Leaf implements Component {
constructor(private name: string) {}
operation(): string { return `Leaf(${this.name})`; }
isComposite(): boolean { return false; }
}
class Composite implements Component {
private children: Component[] = [];
add(c: Component): void { this.children.push(c); }
remove(c: Component): void {
this.children = this.children.filter((ch) => ch !== c);
}
isComposite(): boolean { return true; }
operation(): string {
const results = this.children.map((c) => c.operation());
return `Branch(${results.join("+")})`;
}
}3Step 3: Build a tree and traverse it uniformly
// Build tree: Root → [Branch(Leaf1+Leaf2), Leaf3]
const tree = new Composite();
const branch = new Composite();
branch.add(new Leaf("1"));
branch.add(new Leaf("2"));
tree.add(branch);
tree.add(new Leaf("3"));
console.log(tree.operation());
// Branch(Branch(Leaf(1)+Leaf(2))+Leaf(3))// Composite Pattern – Production
// File-system tree where files and folders share a uniform Node interface.
export interface FsNode {
name: string;
size(): number; // bytes
print(indent?: string): void;
}
// ── Leaf: File ────────────────────────────────────────────────────────────────
export class File implements FsNode {
constructor(
public readonly name: string,
private readonly bytes: number,
) {}
size(): number { return this.bytes; }
print(indent = ""): void {
console.log(`${indent}📄 ${this.name} (${this.bytes} B)`);
}
}
// ── Composite: Directory ──────────────────────────────────────────────────────
export class Directory implements FsNode {
private children: FsNode[] = [];
constructor(public readonly name: string) {}
add(node: FsNode): this {
this.children.push(node);
return this;
}
remove(name: string): boolean {
const before = this.children.length;
this.children = this.children.filter((n) => n.name !== name);
return this.children.length < before;
}
find(name: string): FsNode | undefined {
for (const child of this.children) {
if (child.name === name) return child;
if (child instanceof Directory) {
const found = child.find(name);
if (found) return found;
}
}
return undefined;
}
size(): number {
return this.children.reduce((sum, c) => sum + c.size(), 0);
}
print(indent = ""): void {
console.log(`${indent}📁 ${this.name}/ (${this.size()} B)`);
for (const child of this.children) {
child.print(indent + " ");
}
}
}
// ── Usage ──────────────────────────────────────────────────────────────────────
const root = new Directory("project")
.add(
new Directory("src")
.add(new File("index.ts", 1200))
.add(new File("utils.ts", 800)),
)
.add(
new Directory("tests")
.add(new File("index.test.ts", 600)),
)
.add(new File("package.json", 300));
root.print();
console.log("Total size:", root.size(), "B");Composite Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Composite Pattern in the Real World
“A company’s org chart is a composite structure. An individual employee (leaf) has a salary and a name. A department (composite) also has a name and a budget—calculated by summing the salaries of all its members, which may themselves be other departments. HR can call ‘get budget’ on the CEO’s node and the entire tree is summed recursively.”