Composite Pattern
The Composite Pattern is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.
1. The Core Concept
This pattern is useful when your application's core model can be represented as a tree.
- Leaf: An individual object that does the actual work. It has no children.
- Composite: An object that contains children (Leaves or other Composites). It delegates work to its children.
- Component: A common interface for both Leaves and Composites.
The client treats all objects (Leaves and Composites) uniformly through the Component interface.
2. Analogy: A Box of Gifts
Imagine you order a shipment.
- You receive a big Box.
- Inside, there might be a smaller Box and a Product (Leaf).
- Inside the smaller box, there might be more Products.
If you want to know the total price, you don't need to unpack everything yourself. You just ask the big Box: "What's your price?".
- The Box asks its contents.
- If content is a Product, it returns its price.
- If content is a Box, it asks its contents.
- The prices bubble up to the top.
3. Example (TypeScript)
Let's model a file system.
// The Component Interface
interface FileSystemComponent {
getName(): string;
getSize(): number;
}
// Leaf
class FileItem implements FileSystemComponent {
constructor(private name: string, private size: number) {}
getName(): string { return this.name; }
getSize(): number { return this.size; }
}
// Composite
class Directory implements FileSystemComponent {
private children: FileSystemComponent[] = [];
constructor(private name: string) {}
add(component: FileSystemComponent) {
this.children.push(component);
}
getName(): string { return this.name; }
getSize(): number {
// Delegate to children and sum up
return this.children.reduce((sum, child) => sum + child.getSize(), 0);
}
}
// Usage
const root = new Directory("root");
const file1 = new FileItem("file1.txt", 10);
const subDir = new Directory("subdir");
const file2 = new FileItem("file2.txt", 20);
subDir.add(file2);
root.add(file1);
root.add(subDir);
console.log(root.getSize()); // Output: 30 (10 + 20)
4. Pros and Cons
| Pros | Cons |
|---|---|
| Uniformity: You can treat simple and complex elements the same way. | Generalization: It might be difficult to provide a common interface for classes whose functionality differs too much. |
| Open/Closed: You can introduce new element types without breaking existing code. | Type Safety: You can't restrict the types of components a composite can hold (unless you check at runtime). |
| Recursion: Makes it easy to work with recursive structures. |
5. Recursive Composition
The power of the Composite pattern lies in its ability to create complex tree structures that can be treated as a single object. This is achieved through Recursive Composition.
- Infinite Depth: A Composite can contain other Composites, which can contain other Composites, and so on.
- Simple Client Code: The client code doesn't need to know if it's dealing with a simple Leaf or a complex tree of Composites. It just calls
operation(). - Execution: When
operation()is called on a Composite, it iterates through its children and callsoperation()on them. If a child is also a Composite, it repeats the process. This recursion continues until it hits the Leaves, which perform the actual work.
programming/design-patterns programming/object-oriented-programming programming/iterator-pattern