Composite Pattern: Handle Tree Structures Uniformly and Flexibly
Discover the Composite Pattern - a design pattern that lets you treat individual objects and compositions of objects uniformly, ideal for hierarchical tree structures.
Table of Contents
No table of contents available for this article
1. What Is the Composite Pattern?
The Composite Pattern is a Structural Design Pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. The special feature of Composite is that it lets clients treat individual objects and compositions of objects uniformly.
Usage frequency: Quite high - especially popular in applications with hierarchical structures.
2. Problem to Solve
Imagine you're building an order management system for a gift shop. You have two types of objects:
Individual products: Like a phone, toy, book...
Gift boxes: Can contain multiple products or smaller gift boxes inside
The challenge: How do you calculate the total value of an order when it has such a complex structure?
The conventional approach is to open all boxes, iterate through each product, and sum them up. But this leads to complex code with multiple nested levels, making it difficult to maintain and extend.
Concrete example:
Your order structure looks like this:
Phone (256 USD)
Large Gift Box containing:
Truck Toy (289 USD)
Plane Toy (587 USD)
Small Gift Box inside containing:
Soldier Toy (200 USD)
How can you calculate the total price simply and flexibly?
3. Solution: Composite Pattern
The Composite Pattern solves this by:
Creating a common interface for both individual products and gift boxes
Defining common methods (e.g., CalculateTotalPrice()) that both must implement
For individual products: return their own price
For gift boxes: iterate through internal items, call CalculateTotalPrice() on each item, and sum them up
The biggest benefit: Clients don't need to know whether they're working with individual objects or compositions. Just call the same method!
4. Composite Pattern Architecture
The model includes the following components:
Component: Interface or abstract class defining common methods for all objects in the tree
Leaf: Terminal object with no children. Implements Component methods. Example: Individual product.
Composite: Object that can contain Leafs or other Composites. Implements Component methods by delegating to child components.
Client: Uses Component interface to interact with objects, doesn't care if it's a Leaf or Composite.
5. Advantages and Disadvantages
Advantages:
Simplifies client code: Handles individual and composite objects uniformly
Easy to add new object types: Adheres to the Open/Closed Principle
Flexible with complex structures: Easy to work with multi-level trees
High reusability: Code can be applied to various tree structures
Disadvantages:
Increases complexity: Many interfaces and classes are created
Difficult to generalize: For classes with very different functions, creating a common interface may make code harder to understand
Runtime overhead: May increase overhead when traversing large trees
6. When to Use Composite Pattern?
When you need to represent hierarchical structures in tree form (part-whole hierarchy)
When you want clients to treat uniformly individual objects and compositions
When structures can be nested multiple levels and you want to simplify traversal
Real-world examples:
File system (folders and files)
Graphical interface (containers and widgets)
Company organizational structure (departments and employees)
Multi-level menus (menus and submenus)
7. Code Example in C#
Problem: Calculate the total value of a gift in the store, which can be a single product or a complex gift box.
Step 1: Create Component (Base Class)
csharp
public abstract class GiftBase
{
protected string name;
protected int price;
public GiftBase(string name, int price)
{
this.name = name;
this.price = price;
}
public abstract int CalculateTotalPrice();
}
public interface IGiftOperations
{
void Add(GiftBase gift);
void Remove(GiftBase gift);
}Step 2: Create Leaf (Individual Object)
csharp
public class SingleGift : GiftBase
{
public SingleGift(string name, int price)
: base(name, price)
{
}
public override int CalculateTotalPrice()
{
Console.WriteLine($"{name} with price ${price}");
return price;
}
}Step 3: Create Composite (Composite Object)
csharp
public class CompositeGift : GiftBase, IGiftOperations
{
private List<GiftBase> _gifts;
public CompositeGift(string name, int price)
: base(name, price)
{
_gifts = new List<GiftBase>();
}
public void Add(GiftBase gift)
{
_gifts.Add(gift);
}
public void Remove(GiftBase gift)
{
_gifts.Remove(gift);
}
public override int CalculateTotalPrice()
{
int total = 0;
Console.WriteLine($"{name} contains products:");
foreach (var gift in _gifts)
{
total += gift.CalculateTotalPrice();
}
return total;
}
}Step 4: Client Code
csharp
class Program
{
static void Main(string[] args)
{
var phone = new SingleGift("Phone", 256);
Console.WriteLine($"Price: ${phone.CalculateTotalPrice()}\n");
var rootBox = new CompositeGift("Large Gift Box", 0);
var truckToy = new SingleGift("Truck Toy", 289);
var planeToy = new SingleGift("Plane Toy", 587);
rootBox.Add(truckToy);
rootBox.Add(planeToy);
var childBox = new CompositeGift("Small Gift Box", 0);
var soldierToy = new SingleGift("Soldier Toy", 200);
childBox.Add(soldierToy);
rootBox.Add(childBox);
Console.WriteLine($"\nTotal gift value: ${rootBox.CalculateTotalPrice()}");
}
}Program output:
Phone with price 256 USD Price: 256 USD
Large Gift Box contains products: Truck Toy with price 289 USD Plane Toy with price 587 USD Small Gift Box contains products: Soldier Toy with price 200 USD
Total gift value: 1076 USD
8. Related Design Patterns
Builder: Used to construct complex Composite trees step-by-step recursively
Chain of Responsibility (CoR): Combined with Composite to pass requests from leaves up to parent nodes in the tree
Iterator: Used to traverse elements in a Composite tree systematically
Visitor: Performs complex operations on the entire Composite tree without changing its structure
Flyweight: Optimizes memory when there are many similar leaf nodes in the tree
Decorator: Similar to Composite but has only one child component. Decorator adds functionality, while Composite aggregates results
Prototype: Useful when you need to clone complex Composite structures instead of rebuilding from scratch
9. Conclusion
The Composite Pattern is a powerful solution for handling complex hierarchical structures. By creating a common interface for both individual objects and compositions, this pattern makes code simpler, easier to maintain, and extend. While it may increase initial complexity, the long-term benefits are well worth it, especially in systems with tree structures.
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns