Composite
The Composite pattern lets you treat single objects and groups of objects the same way. Trees, file systems, scene graphs, UI hierarchies — anywhere a thing might be a leaf or a container of more things.
A file-system tree + a renderable tree
EXAMPLE
// 1) File-system tree — same interface for files and folders
class FsNode {
constructor(name) { this.name = name; }
size() { throw new Error('abstract'); }
print(prefix = '') { throw new Error('abstract'); }
}
class File extends FsNode {
constructor(name, bytes) { super(name); this.bytes = bytes; }
size() { return this.bytes; }
print(prefix) { console.log(\`${prefix}- ${this.name} (${this.bytes}B)\`); }
}
class Folder extends FsNode {
constructor(name) { super(name); this.children = []; }
add(child) { this.children.push(child); return this; }
size() { return this.children.reduce((s, c) => s + c.size(), 0); }
print(prefix = '') {
console.log(\`${prefix}+ ${this.name}/\`);
for (const c of this.children) c.print(prefix + ' ');
}
}
const root = new Folder('src')
.add(new File('main.ts', 1024))
.add(new Folder('lib')
.add(new File('util.ts', 512))
.add(new File('http.ts', 2048)))
.add(new File('README.md', 256));
console.log('total:', root.size());
root.print();
// 2) Renderable UI tree — every node renders itself + children
class Box {
constructor() { this.children = []; }
add(c) { this.children.push(c); return this; }
render() {
return \`<div>${this.children.map(c => c.render()).join('')}</div>\`;
}
}
class Text {
constructor(s) { this.s = s; }
render() { return this.s; }
}
const tree = new Box()
.add(new Text('Header'))
.add(new Box()
.add(new Text('left'))
.add(new Text('right')));
console.log(tree.render());
// 3) Real-world appearances
// - DOM (HTMLElement has children of HTMLElement)
// - React components (every component renders more components)
// - Three.js scene graph (Object3D + Group)
// - GUI menu systems
// - JSON / AST / compiler IRs
// 4) Trade-off
// Pro: uniform code, easy recursion.
// Con: leaf operations must implement the full container interface (or stub it).
Why it matters
When you find yourself writing “if (item is a folder) { recurse }” everywhere, that’s Composite begging to be extracted. Make leaves and containers share an interface; recursion happens naturally.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Treat a single object and a group of objects uniformly.
class Component { render() {} }
class Group extends Component {
constructor() { super(); this.children = []; }
add(c) { this.children.push(c); }
render() { return this.children.map(c => c.render()).join(''); }
}
Try it Yourself »
Discussion
Loading…