d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
8.2 KiB
8.2 KiB
Composite Pattern
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions uniformly.
Problem
You have a file system with files and directories. A directory can contain files and other directories. You need to calculate the total size of any node — whether it's a single file or an entire directory tree — using the same interface.
Solution
Define a common Component interface with a size() method. File is a leaf that returns its own size. Directory is a composite that sums the sizes of its children.
┌──────────────────┐
│ Component (intf) │
│ + size(): int │
│ + name(): string │
└──────┬───────────┘
│
┌───────┴───────┐
│ │
┌────┴────┐ ┌──────┴──────┐
│ File │ │ Directory │
│ (leaf) │ │ (composite) │
└─────────┘ │ + children │
│ + add() │
└─────────────┘
When to Use
- You need to represent part-whole hierarchies (trees).
- You want clients to treat individual objects and compositions uniformly.
- You want to add new leaf or composite types without changing client code.
When to Avoid
- The structure is flat with no nesting — a simple list suffices.
- Leaf and composite operations differ significantly, making a uniform interface forced.
- You need strict type safety between leaf and composite at compile time.
Pseudocode
interface Component:
method size(): int
method name(): string
class File implements Component:
field _name: string
field _size: int
constructor(name, size):
this._name = name
this._size = size
method size(): return _size
method name(): return _name
class Directory implements Component:
field _name: string
field children: list<Component>
constructor(name):
this._name = name
this.children = []
method add(child: Component):
children.append(child)
method size():
total = 0
for child in children:
total += child.size()
return total
method name(): return _name
// Usage
root = Directory("root")
root.add(File("readme.md", 100))
src = Directory("src")
src.add(File("main.py", 500))
src.add(File("utils.py", 300))
root.add(src)
print root.name() + " total size: " + root.size()
Python
from abc import ABC, abstractmethod
class Component(ABC):
"""Common interface for files and directories."""
@abstractmethod
def size(self) -> int: ...
@abstractmethod
def name(self) -> str: ...
def display(self, indent: int = 0) -> None:
print(f"{' ' * indent}{self.name()} ({self.size()} bytes)")
class File(Component):
"""Leaf node."""
def __init__(self, name: str, size: int) -> None:
self._name = name
self._size = size
def size(self) -> int:
return self._size
def name(self) -> str:
return self._name
class Directory(Component):
"""Composite node that contains children."""
def __init__(self, name: str) -> None:
self._name = name
self._children: list[Component] = []
def add(self, child: Component) -> "Directory":
self._children.append(child)
return self
def size(self) -> int:
return sum(child.size() for child in self._children)
def name(self) -> str:
return self._name
def display(self, indent: int = 0) -> None:
print(f"{' ' * indent}{self.name()}/ ({self.size()} bytes)")
for child in self._children:
child.display(indent + 1)
if __name__ == "__main__":
root = Directory("root")
root.add(File("readme.md", 100))
root.add(File(".gitignore", 50))
src = Directory("src")
src.add(File("main.py", 500))
src.add(File("utils.py", 300))
tests = Directory("tests")
tests.add(File("test_main.py", 400))
src.add(tests)
root.add(src)
root.display()
print(f"\nTotal size: {root.size()} bytes")
Output:
root/ (1350 bytes)
readme.md (100 bytes)
.gitignore (50 bytes)
src/ (1200 bytes)
main.py (500 bytes)
utils.py (300 bytes)
tests/ (400 bytes)
test_main.py (400 bytes)
Total size: 1350 bytes
Go
package main
import (
"fmt"
"strings"
)
// Component is the common interface.
type Component interface {
Size() int
Name() string
Display(indent int)
}
// File is a leaf node.
type File struct {
name string
size int
}
func NewFile(name string, size int) *File {
return &File{name: name, size: size}
}
func (f *File) Size() int { return f.size }
func (f *File) Name() string { return f.name }
func (f *File) Display(indent int) {
fmt.Printf("%s%s (%d bytes)\n", strings.Repeat(" ", indent), f.name, f.size)
}
// Directory is a composite node.
type Directory struct {
name string
children []Component
}
func NewDirectory(name string) *Directory {
return &Directory{name: name}
}
func (d *Directory) Add(c Component) *Directory {
d.children = append(d.children, c)
return d
}
func (d *Directory) Size() int {
total := 0
for _, child := range d.children {
total += child.Size()
}
return total
}
func (d *Directory) Name() string { return d.name }
func (d *Directory) Display(indent int) {
fmt.Printf("%s%s/ (%d bytes)\n", strings.Repeat(" ", indent), d.name, d.Size())
for _, child := range d.children {
child.Display(indent + 1)
}
}
func main() {
root := NewDirectory("root")
root.Add(NewFile("readme.md", 100))
root.Add(NewFile(".gitignore", 50))
src := NewDirectory("src")
src.Add(NewFile("main.py", 500))
src.Add(NewFile("utils.py", 300))
tests := NewDirectory("tests")
tests.Add(NewFile("test_main.py", 400))
src.Add(tests)
root.Add(src)
root.Display(0)
fmt.Printf("\nTotal size: %d bytes\n", root.Size())
}
Output:
root/ (1350 bytes)
readme.md (100 bytes)
.gitignore (50 bytes)
src/ (1200 bytes)
main.py (500 bytes)
utils.py (300 bytes)
tests/ (400 bytes)
test_main.py (400 bytes)
Total size: 1350 bytes
JavaScript
class File {
constructor(name, size) {
this._name = name;
this._size = size;
}
size() {
return this._size;
}
name() {
return this._name;
}
display(indent = 0) {
console.log(`${" ".repeat(indent)}${this.name()} (${this.size()} bytes)`);
}
}
class Directory {
constructor(name) {
this._name = name;
this._children = [];
}
add(child) {
this._children.push(child);
return this;
}
size() {
return this._children.reduce((sum, child) => sum + child.size(), 0);
}
name() {
return this._name;
}
display(indent = 0) {
console.log(
`${" ".repeat(indent)}${this.name()}/ (${this.size()} bytes)`
);
for (const child of this._children) {
child.display(indent + 1);
}
}
}
// --- Main ---
const root = new Directory("root");
root.add(new File("readme.md", 100));
root.add(new File(".gitignore", 50));
const src = new Directory("src");
src.add(new File("main.py", 500));
src.add(new File("utils.py", 300));
const tests = new Directory("tests");
tests.add(new File("test_main.py", 400));
src.add(tests);
root.add(src);
root.display();
console.log(`\nTotal size: ${root.size()} bytes`);
Output:
root/ (1350 bytes)
readme.md (100 bytes)
.gitignore (50 bytes)
src/ (1200 bytes)
main.py (500 bytes)
utils.py (300 bytes)
tests/ (400 bytes)
test_main.py (400 bytes)
Total size: 1350 bytes
Related Patterns
- Iterator — use iterator to traverse composite trees without exposing the tree structure to the client.
- Visitor — visitor lets you add operations to composite trees without modifying node classes.
- Decorator — decorator adds responsibilities to a single object; composite organises objects into tree structures.
- Flyweight — use flyweight to share leaf node data when the composite tree contains many identical leaves.