Composite

Composite Pattern

Introduction

The Composite pattern is a structural design pattern that lets you compose objects into tree structures to represent part-whole hierarchies. This pattern is useful for treating individual objects and compositions of objects uniformly.

Purpose

  • To compose objects into tree structures to represent part-whole hierarchies.
  • To let clients treat individual objects and compositions of objects uniformly.

Use Cases

  • When you want to represent part-whole hierarchies of objects.
  • When you want clients to ignore the difference between compositions of objects and individual objects.

Advantages/Disadvantages

Advantages:

  • Simplifies the client code, as it can treat composite structures and individual objects the same way.
  • Makes it easier to add new kinds of components.

Disadvantages:

  • Can make your design overly general. It can be difficult to restrict the components of a composite to only certain types.

Implementation in JavaScript

javascript
	class Component {
	  operation() {}
	}
	
	class Leaf extends Component {
	  operation() {
	    return 'Leaf';
	  }
	}
	
	class Composite extends Component {
	  constructor() {
	    super();
	    this.children = [];
	  }
	
	  add(component) {
	    this.children.push(component);
	  }
	
	  operation() {
	    return `Branch(${this.children
	      .map((child) => child.operation())
	      .join('+')})`;
	  }
	}
	
	// Usage
	const leaf1 = new Leaf();
	const leaf2 = new Leaf();
	const composite = new Composite();
	
	composite.add(leaf1);
	composite.add(leaf2);
	
	console.log(composite.operation()); // "Branch(Leaf+Leaf)"

Practical Example

Let’s say you’re building a backend system for a file directory where both individual files and directories (which can contain other files or directories) need to be treated similarly.

javascript
	class FileSystemComponent {
	  getSize() {}
	}
	
	class File extends FileSystemComponent {
	  constructor(size) {
	    super();
	    this.size = size;
	  }
	
	  getSize() {
	    return this.size;
	  }
	}
	
	class Directory extends FileSystemComponent {
	  constructor() {
	    super();
	    this.components = [];
	  }
	
	  add(component) {
	    this.components.push(component);
	  }
	
	  getSize() {
	    return this.components.reduce(
	      (total, component) => total + component.getSize(),
	      0,
	    );
	  }
	}
	
	// Usage
	const file1 = new File(100);
	const file2 = new File(200);
	const directory = new Directory();
	
	directory.add(file1);
	directory.add(file2);
	
	console.log(directory.getSize()); // 300 (sum of file sizes)

In this example, FileSystemComponent is the abstract class for both files and directories. File and Directory classes extend it, where Directory can have a collection of FileSystemComponent objects, allowing directories to contain files or other directories. This pattern lets you treat both files and directories uniformly.