Skip to main content

Real World Implementation Of Composite Design Pattern

Files and folders can contain other files and folders, forming a tree-like hierarchy. The Composite Pattern can be used here as each component in this structure (either a file or a folder) can be treated uniformly.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

Real Word Application

A good example of the Composite Pattern in the real world is the way a file system is structured. Files and folders can contain other files and folders, forming a tree-like hierarchy. The Composite Pattern can be used here as each component in this structure (either a file or a folder) can be treated uniformly.

Here's an example of how this might be implemented in TypeScript:

// Component
interface FileSystemComponent {
getName(): string;

getSize(): number;
}

// Leaf
class File implements FileSystemComponent {
constructor(private name: string, private size: number) {}

getName(): string {
return this.name;
}

getSize(): number {
return this.size;
}
}

// Composite
interface CompositeFileSystemComponent extends FileSystemComponent {
addComponent(component: FileSystemComponent): void;

removeComponent(component: FileSystemComponent): void;

getComponents(): FileSystemComponent[];
}

class Folder implements CompositeFileSystemComponent {
private components: FileSystemComponent[] = [];

constructor(private name: string) {}

getName(): string {
return this.name;
}

getSize(): number {
// calculate size by summing sizes of all components
return this.components.reduce(
(total, component) => total + component.getSize(),
0
);
}

addComponent(component: FileSystemComponent) {
this.components.push(component);
}

removeComponent(component: FileSystemComponent) {
const index = this.components.indexOf(component);
if (index !== -1) {
this.components.splice(index, 1);
}
}

getComponents(): FileSystemComponent[] {
return this.components;
}
}

Usage:

let file1 = new File("file1.txt", 500);
let file2 = new File("file2.txt", 1200);
let file3 = new File("file3.txt", 3400);

let folder = new Folder("myFolder");
folder.addComponent(file1);
folder.addComponent(file2);
folder.addComponent(file3);

console.log(`Folder '${folder.getName()}' contains: `);
folder
.getComponents()
.forEach((component) =>
console.log(
`- ${component.getName()} with size of ${component.getSize()} bytes`
)
);

console.log(
`Total size of folder '${folder.getName()}': ${folder.getSize()} bytes`
);

In this example, File and Folder are treated uniformly as FileSystemComponents. You can perform common operations (like getting name and size) on both files and folders. A Folder can contain both Files and other Folders, making it the composite in this scenario.

Advantages Of The Composite Pattern

The Composite Pattern has several key advantages:

Simplifies the client code

The client can treat composite structures and individual objects uniformly. In our filesystem example, both File and Folder implement the FileSystemComponent interface. The client does not need to worry if an object is a simple file or a complex folder containing other components.

let file: FileSystemComponent = new File("file1.txt", 500);
let folder: FileSystemComponent = new Folder("myFolder");

In this snippet, both file and folder are treated as FileSystemComponents. We can call getName() and getSize() on both, regardless of their complexity.

Makes it easier to add new types of components

New types of leaf or composite objects can be easily added, as they just need to implement the component interface. Suppose we want to add a Link type to our filesystem. It would be as easy as creating a new class implementing the FileSystemComponent interface.

class Link implements FileSystemComponent {
constructor(private name: string, private size: number) {}

getName(): string {
return this.name;
}

getSize(): number {
return this.size;
}
}

Easily represents hierarchies

Composite Pattern is a natural fit for systems that have a hierarchical structure. The example of a filesystem is a perfect match. A Folder can contain other Folders, forming a tree-like structure.

let folder1 = new Folder("folder1");
let folder2 = new Folder("folder2");
folder1.addComponent(folder2);

Here, folder1 contains folder2, illustrating a simple hierarchy.

Remember, the Composite Pattern isn't always the best choice. If your system doesn't have a clear tree-like hierarchy, or if there's no need to operate on all the components uniformly, another pattern may be a better fit.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle