The Composite Pattern is a structural design pattern used to compose objects into tree-like structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
The Composite Pattern is a structural design pattern that allows you to compose objects into tree-like structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly, enabling recursive processing of complex structures as if they were single objects.
Important Components
- Component: An abstract class or interface defining common operations for both leaf and composite objects.
- Leaf: A basic element of the hierarchy that has no children and implements the Component interface.
- Composite: A container that holds a collection of Components (Leaves or other Composites) and implements the Component interface, often delegating operations to its children.
- Client: Interacts with the Component interface, unaware of whether it’s dealing with a Leaf or a Composite.
How It Works
- The Component interface declares methods for operations (e.g., display, add, remove).
- Leaf objects implement these methods for individual elements.
- Composite objects implement the same methods, typically by iterating over their children and delegating the operation.
- The client works with the Component interface, allowing uniform treatment of leaves and composites, often recursively traversing the hierarchy.
Sample Implementation
// Component Interface
interface FileSystemEntry {
void display(int indent);
String getName();
}
// Leaf
class File implements FileSystemEntry {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void display(int indent) {
System.out.println(" ".repeat(indent) + "- File: " + name);
}
@Override
public String getName() {
return name;
}
}
// Composite
class Directory implements FileSystemEntry {
private String name;
private List<FileSystemEntry> entries;
public Directory(String name) {
this.name = name;
this.entries = new ArrayList<>();
}
public void addEntry(FileSystemEntry entry) {
entries.add(entry);
}
public void removeEntry(FileSystemEntry entry) {
entries.remove(entry);
}
@Override
public void display(int indent) {
System.out.println(" ".repeat(indent) + "+ Directory: " + name);
for (FileSystemEntry entry : entries) {
entry.display(indent + 1);
}
}
@Override
public String getName() {
return name;
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Create files (Leaves)
FileSystemEntry file1 = new File("document.txt");
FileSystemEntry file2 = new File("image.png");
FileSystemEntry file3 = new File("data.csv");
// Create directories (Composites)
Directory root = new Directory("root");
Directory home = new Directory("home");
Directory user = new Directory("user");
// Build hierarchy
root.addEntry(home);
home.addEntry(user);
home.addEntry(file1);
user.addEntry(file2);
user.addEntry(file3);
// Display the hierarchy
root.display(0);
}
}
Code language: PHP (php)
Use Case and Implementation:
Representing and managing hierarchical structures like customer portfolios containing multiple accounts and investments.
//CompositePatternDemo.java import java.util.ArrayList; import java.util.List; // Component interface interface PortfolioComponent { double getValue(); // Method to get total value of the portfolio } // Leaf class representing an investment class Investment implements PortfolioComponent { private String name; private double value; public Investment(String name, double value) { this.name = name; this.value = value; } @Override public double getValue() { return value; } public String getName() { return name; } } // Composite class representing a portfolio containing investments and sub-portfolios class Portfolio implements PortfolioComponent { private String name; private List<PortfolioComponent> components; public Portfolio(String name) { this.name = name; this.components = new ArrayList<>(); } public void addComponent(PortfolioComponent component) { components.add(component); } public void removeComponent(PortfolioComponent component) { components.remove(component); } @Override public double getValue() { double totalValue = 0.0; for (PortfolioComponent component : components) { totalValue += component.getValue(); } return totalValue; } } public class CompositePatternDemo { public static void main(String[] args) { // Create leaf investments Investment stock1 = new Investment("Tech Stock", 5000); Investment stock2 = new Investment("Pharma Stock", 3000); // Create composite portfolio Portfolio portfolio = new Portfolio("Customer Portfolio"); // Add investments to the portfolio portfolio.addComponent(stock1); portfolio.addComponent(stock2); // Create another sub-portfolio Portfolio subPortfolio = new Portfolio("Sub-Portfolio"); // Add investments to the sub-portfolio Investment bond1 = new Investment("Corporate Bond", 2000); Investment fund1 = new Investment("Mutual Fund", 4000); subPortfolio.addComponent(bond1); subPortfolio.addComponent(fund1); // Add sub-portfolio to the main portfolio portfolio.addComponent(subPortfolio); // Calculate and print total value of the main portfolio double totalValue = portfolio.getValue(); System.out.println("Total value of the portfolio: $" + totalValue); } } /* C:\>javac CompositePatternDemo.java C:\>java CompositePatternDemo Total value of the portfolio: $14000.0 */
Pros
- Uniformity: Clients can treat individual objects and compositions identically.
- Flexibility: Easily add new components (leaves or composites) to the hierarchy.
- Recursive Processing: Simplifies operations like traversal or calculations over the entire structure.
- Scalability: Supports complex tree-like structures without changing client code.
Cons
- Complexity: Can make the design overly general, as all components must conform to the same interface.
- Type Safety: May require type checking or casting to distinguish between leaves and composites.
- Performance: Recursive operations on large hierarchies can be costly.
When to Use
- When you need to represent part-whole hierarchies (e.g., file systems, organizational structures, UI components).
- When clients should treat individual objects and compositions uniformly.
- When you want to perform recursive operations on a tree-like structure.
Real-World Example
- File Systems: Files (leaves) and directories (composites) are treated uniformly for operations like displaying or calculating size.
- UI Frameworks: Widgets (leaves) and containers (composites) in a GUI are managed as a hierarchy (e.g., HTML DOM, Java Swing).
- Organizational Structures: Employees (leaves) and departments (composites) in a company hierarchy.
The Composite Pattern is a structural design pattern that lets you treat individual objects and compositions of objects uniformly. In Java, it’s particularly useful for building tree-like structures such as file systems, GUI components, and organization hierarchies, where part-whole relationships need to be managed recursively. By defining a common interface for both simple and complex objects, the Composite Pattern simplifies client-side operations. It promotes flexibility, extensibility, and elegant recursive behavior, making your code easier to maintain and scale.