Composite ā Tree Structures of Objects
The Composite pattern in C#: treat individual objects and compositions of objects uniformly. Build menu hierarchies, file systems, and organisation charts with a single interface.
Composite ā Tree Structures of Objects
Composite composes objects into tree structures and lets clients treat individual objects and compositions uniformly. The key: both leaves and branches implement the same interface.
Core Structure
// Component ā the common interface for leaves and composites
public interface IMenuComponent
{
string Name { get; }
decimal Price { get; }
void Print(int indent = 0);
}
// Leaf ā no children
public class MenuItem : IMenuComponent
{
public string Name { get; }
public decimal Price { get; }
public MenuItem(string name, decimal price)
{
Name = name;
Price = price;
}
public void Print(int indent = 0)
=> Console.WriteLine($"{new string(' ', indent * 2)}- {Name}: £{Price:F2}");
}
// Composite ā has children (which can be leaves or other composites)
public class MenuCategory : IMenuComponent
{
private readonly List<IMenuComponent> _children = new();
public string Name { get; }
public decimal Price => _children.Sum(c => c.Price); // aggregate
public MenuCategory(string name) => Name = name;
public void Add(IMenuComponent component) => _children.Add(component);
public void Remove(IMenuComponent component) => _children.Remove(component);
public void Print(int indent = 0)
{
Console.WriteLine($"{new string(' ', indent * 2)}+ {Name} (total: £{Price:F2})");
foreach (var child in _children)
child.Print(indent + 1);
}
}
// Build the tree
var menu = new MenuCategory("Restaurant Menu");
var starters = new MenuCategory("Starters");
starters.Add(new MenuItem("Soup", 6.50m));
starters.Add(new MenuItem("Bruschetta", 7.00m));
var mains = new MenuCategory("Mains");
mains.Add(new MenuItem("Steak", 24.00m));
mains.Add(new MenuItem("Salmon", 18.50m));
var veggie = new MenuCategory("Vegetarian");
veggie.Add(new MenuItem("Risotto", 14.00m));
veggie.Add(new MenuItem("Pasta", 12.00m));
mains.Add(veggie); // composite inside composite
menu.Add(starters);
menu.Add(mains);
menu.Print();
// + Restaurant Menu (total: £82.00)
// + Starters (total: £13.50)
// - Soup: £6.50
// - Bruschetta: £7.00
// + Mains (total: £68.50)
// - Steak: £24.00
// - Salmon: £18.50
// + Vegetarian (total: £26.00)
// - Risotto: £14.00
// - Pasta: £12.00File System Example
public interface IFileSystemNode
{
string Name { get; }
long Size { get; } // bytes
}
public class File : IFileSystemNode
{
public string Name { get; }
public long Size { get; }
public File(string name, long size) { Name = name; Size = size; }
}
public class Directory : IFileSystemNode
{
private readonly List<IFileSystemNode> _children = new();
public string Name { get; }
public long Size => _children.Sum(c => c.Size); // recursive aggregate
public Directory(string name) => Name = name;
public void Add(IFileSystemNode node) => _children.Add(node);
public IEnumerable<IFileSystemNode> GetChildren() => _children;
}
// Usage
var root = new Directory("root");
var src = new Directory("src");
src.Add(new File("Program.cs", 2048));
src.Add(new File("Startup.cs", 4096));
root.Add(src);
root.Add(new File("README.md", 512));
Console.WriteLine($"Total size: {root.Size} bytes"); // 6656Expression Trees (Built-In Composite)
// LINQ expression trees are a composite in the .NET framework itself
// Each node (BinaryExpression, MethodCallExpression, etc.) implements Expression
// x => x.Age > 18 && x.IsActive
Expression<Func<User, bool>> expr =
u => u.Age > 18 && u.IsActive;
// The tree:
// AndAlso (composite)
// āāā GreaterThan (composite)
// ā āāā u.Age (leaf)
// ā āāā 18 (leaf)
// āāā u.IsActive (leaf)When to Use
ā Hierarchical data: menus, categories, organisational charts, file systems
ā You want to treat single items and groups identically
ā Recursive aggregation (total price, total size, count)
ā Tree traversal ā render, search, transform
ā Simple flat collections ā use List directly
ā When leaves and composites need very different APIs Interview Answer
"The Composite pattern lets you build tree structures where both leaves (single items) and branches (containers of items) implement the same interface. The classic examples: file system (File and Directory both implement IFileSystemNode), menu hierarchies (MenuItem and MenuCategory), and organisational charts. The benefit is uniform treatment ā client code calls
node.Sizeornode.Pricewithout knowing whether it's a leaf or a composite; the composite aggregates from its children recursively. In .NET, expression trees (used by LINQ) are a built-in application of Composite. The trade-off: the common interface must work for both leaves and composites, which can feel artificial when they have different capabilities ā add methods carefully to the interface."
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.