StructuralC#verifiedVerified
Composite Pattern in C#
Composes objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions uniformly.
How to Implement the Composite Pattern in C#
1Step 1: Component interface shared by leaves and composites
public interface IFileSystemItem
{
string Name { get; }
long GetSize();
}2Step 2: Leaf node
public class File(string name, long size) : IFileSystemItem
{
public string Name => name;
public long GetSize() => size;
}3Step 3: Composite node contains children
public class Directory(string name) : IFileSystemItem
{
private readonly List<IFileSystemItem> _children = [];
public string Name => name;
public void Add(IFileSystemItem item) => _children.Add(item);
public void Remove(IFileSystemItem item) => _children.Remove(item);
// Recursively compute size
public long GetSize() => _children.Sum(c => c.GetSize());
}
// Usage:
// var root = new Directory("root");
// root.Add(new File("a.txt", 100));
// var sub = new Directory("sub");
// sub.Add(new File("b.txt", 200));
// root.Add(sub);
// Console.WriteLine(root.GetSize()); // 300using Microsoft.Extensions.Logging;
// [step] Define component interface for organization chart
public interface IOrgComponent
{
string Name { get; }
string Title { get; }
decimal GetTotalCompensation();
int GetHeadcount();
IEnumerable<IOrgComponent> GetSubordinates();
void Accept(IOrgVisitor visitor, int depth = 0);
}
public interface IOrgVisitor
{
void Visit(Employee employee, int depth);
void Visit(Department department, int depth);
}
// [step] Leaf: individual employee
public sealed class Employee(
string name, string title, decimal salary) : IOrgComponent
{
public string Name => name;
public string Title => title;
public decimal Salary => salary;
public decimal GetTotalCompensation() => salary;
public int GetHeadcount() => 1;
public IEnumerable<IOrgComponent> GetSubordinates() => [];
public void Accept(IOrgVisitor visitor, int depth = 0) =>
visitor.Visit(this, depth);
}
// [step] Composite: department contains employees and sub-departments
public sealed class Department(
string name, string title) : IOrgComponent
{
private readonly List<IOrgComponent> _members = [];
public string Name => name;
public string Title => title;
public IReadOnlyList<IOrgComponent> Members => _members.AsReadOnly();
public Department Add(IOrgComponent member)
{
_members.Add(member);
return this;
}
public bool Remove(IOrgComponent member) => _members.Remove(member);
public decimal GetTotalCompensation() =>
_members.Sum(m => m.GetTotalCompensation());
public int GetHeadcount() =>
_members.Sum(m => m.GetHeadcount());
public IEnumerable<IOrgComponent> GetSubordinates() => _members;
public void Accept(IOrgVisitor visitor, int depth = 0)
{
visitor.Visit(this, depth);
foreach (var member in _members)
member.Accept(visitor, depth + 1);
}
}
// [step] Visitor for generating org chart report
public sealed class OrgChartPrinter(ILogger<OrgChartPrinter> logger)
: IOrgVisitor
{
private readonly List<string> _lines = [];
public void Visit(Employee employee, int depth)
{
var indent = new string(' ', depth * 2);
var line = $"{indent}- {employee.Name} ({employee.Title}) " +
$"[${employee.Salary:N0}]";
_lines.Add(line);
logger.LogDebug("{Line}", line);
}
public void Visit(Department department, int depth)
{
var indent = new string(' ', depth * 2);
var line = $"{indent}[{department.Name}] " +
$"({department.GetHeadcount()} people, " +
$"${department.GetTotalCompensation():N0} total)";
_lines.Add(line);
logger.LogDebug("{Line}", line);
}
public string GetReport() => string.Join("\n", _lines);
}Composite Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Composite Pattern in the Real World
“A company’s org chart is a composite structure. An individual employee (leaf) has a salary and a name. A department (composite) also has a name and a budget—calculated by summing the salaries of all its members, which may themselves be other departments. HR can call ‘get budget’ on the CEO’s node and the entire tree is summed recursively.”