The Composite Design Pattern is a structural design pattern that allows you to treat individual objects and compositions of objects uniformly. It is particularly useful for representing part-whole hierarchies, where individual objects and collections of objects are treated in a similar way. This pattern is commonly used when you need to build a tree-like structure.
Key Concepts:
- Component:
This is the common interface for both leaf objects and composite objects.
It defines the operations that can be performed on both individual objects
and groups of objects.
- Leaf:
A leaf is an individual object in the structure. It implements the Component
interface and doesn't have any children.
- Composite:
A composite object is a collection of Component objects (which could be
either leaf or other composite objects). It implements the Component
interface and can have child components.
Example of Composite Pattern in JavaScript
Let's consider a scenario where we have a file system, with
files (leaf) and folders (composite). Both files and folders can have the getSize
method, but a folder can contain other folders or files.
Without the Composite Pattern (Non-Uniform Handling):
class File {
constructor(name,
size) {
this.name =
name;
this.size =
size;
}
getSize() {
return this.size;
}
}
class Folder {
constructor(name)
{
this.name =
name;
this.children
= [];
}
add(child) {
this.children.push(child);
}
getSize() {
let
totalSize = 0;
for (let
child of this.children) {
totalSize += child.getSize();
}
return
totalSize;
}
}
const file1 = new File("file1.txt", 10);
const file2 = new File("file2.txt", 20);
const folder1 = new Folder("folder1");
folder1.add(file1);
folder1.add(file2);
console.log(`Folder size: ${folder1.getSize()}`); // 30
In this example, the Folder class has to manage its children
manually by iterating over them and summing up their sizes. If we wanted to add
other kinds of elements, we'd need to modify this logic. This becomes
cumbersome as the complexity grows.
With the Composite Pattern (Uniform Handling):
Now, let's refactor the code using the Composite Pattern,
where both File and Folder share the same interface and are treated uniformly.
// Component: Common interface
class FileSystemComponent {
constructor(name)
{
this.name =
name;
}
getSize() {
throw new Error("This
method should be overridden!");
}
}
// Leaf: File object
class File extends FileSystemComponent {
constructor(name,
size) {
super(name);
this.size =
size;
}
getSize() {
return this.size;
}
}
// Composite: Folder object that can contain other
components
class Folder extends FileSystemComponent {
constructor(name)
{
super(name);
this.children
= [];
}
add(child) {
this.children.push(child);
}
getSize() {
let
totalSize = 0;
for (let
child of this.children) {
totalSize
+= child.getSize();
}
return
totalSize;
}
}
// Usage:
const file1 = new File("file1.txt", 10);
const file2 = new File("file2.txt", 20);
const folder1 = new Folder("folder1");
folder1.add(file1);
folder1.add(file2);
const file3 = new File("file3.txt", 15);
const folder2 = new Folder("folder2");
folder2.add(file3);
folder2.add(folder1); // folder2 contains folder1
console.log(`File3 size: ${file3.getSize()}`); // 15
console.log(`Folder1 size: ${folder1.getSize()}`); // 30
console.log(`Folder2 size: ${folder2.getSize()}`); // 45
(15 + 30)
Explanation:
- FileSystemComponent:
This is the Component interface, providing the getSize method,
which is overridden by both File and Folder.
- File:
The Leaf class. It has a name and a size. The getSize method
returns the size of the file.
- Folder:
The Composite class. It has a collection of children (which could
be File or other Folder objects). It overrides the getSize method to
calculate the total size by recursively calling getSize on its children.
Advantages of the Composite Pattern:
- Uniformity:
Both leaf and composite objects implement the same interface, making the
handling of individual objects and compositions transparent and uniform.
- Flexibility:
You can add new types of components (like different types of files or
folders) without modifying existing code. New components can simply be
added to the hierarchy.
- Ease
of Use: The composite object (e.g., Folder) can be treated the same as
the individual leaf objects (e.g., File) in client code, simplifying
operations like calculating total size, printing contents, etc.
- Recursive
Structure: The pattern naturally supports recursive structures, where
composites can contain other composites.
When to Use the Composite Pattern:
- When
you need to represent part-whole hierarchies (like a filesystem,
organization chart, or product catalog).
- When
you want to treat individual objects and groups of objects uniformly.
- When
you need to add new types of components to a structure without changing
existing code.
No comments:
Post a Comment