The Composite design pattern is a structural pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. This pattern enables clients to treat individual objects and compositions of objects uniformly. The key components of this pattern are:

Example Scenario 1

Implementation

Let's implement a simple example where we have a set of files and folders that can be represented as a tree structure.

// Component
class FileSystemItem {
  getName() {}
  getSize() {}
}

// Leaf
class File extends FileSystemItem {
  constructor(name, size) {
    super();
    this.name = name;
    this.size = size;
  }
  getName() {
    return this.name;
  }
  getSize() {
    return this.size;
  }
}

// Composite
class Folder extends FileSystemItem {
  constructor(name) {
    super();
    this.name = name;
    this.items = [];
  }
  add(item) {
    this.items.push(item);
  }
  getName() {
    return this.name;
  }
  getSize() {
    return this.items.reduce((total, item) => total + item.getSize(), 0);
  }
}

Test case

// Client
const file1 = new File("file1.txt", 100);
const file2 = new File("file2.txt", 200);
const file3 = new File("file3.txt", 300);

const folder1 = new Folder("folder1");
folder1.add(file1);
folder1.add(file2);

const folder2 = new Folder("folder2");
folder2.add(folder1);
folder2.add(file3);

console.log(`${folder2.getName()} size: ${folder2.getSize()} bytes`);

Explanation of the Implementation

In this implementation, we have a FileSystemItem class that acts as the component interface, defining methods for getting the name and size of items. The File class represents leaf nodes that contain individual files with a name and size.

The Folder class represents composite nodes that can contain multiple files or folders. Each folder maintains a list of its child items and can add new items to the list.

The getSize method in the Folder class calculates the total size of all its child items by iterating over them and summing their sizes. The client code creates instances of files and folders, composes them into a tree structure, and then retrieves the total size of the top-level folder.

Example Scenario 2

Implementation

Let's create a more advanced implementation of the Composite design pattern by simulating a graphic editor where we can group and manipulate graphic objects like circles and squares.

// Component
class Graphic {
  draw() {}
  add(graphic) {}
  remove(graphic) {}
  getChild(index) {}
}

// Leaf
class Circle extends Graphic {
  draw() {
    console.log("Drawing a Circle");
  }
}
class Square extends Graphic {
  draw() {
    console.log("Drawing a Square");
  }
}

// Composite
class GraphicGroup extends Graphic {
  constructor() {
    super();
    this.graphics = [];
  }
  add(graphic) {
    this.graphics.push(graphic);
  }
  remove(graphic) {
    this.graphics = this.graphics.filter((g) => g !== graphic);
  }
  getChild(index) {
    return this.graphics[index];
  }
  draw() {
    console.log("Drawing a Graphic Group:");
    this.graphics.forEach((graphic) => graphic.draw());
  }
}

Test case