πΏ Composite Pattern: Unified Organization Chart
π Overview
The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly, simplifying code that deals with complex recursive structures.
Core Concepts
- Component Interface: The common base or interface that declares operations for both simple and complex objects in the composition.
- Leaf: The basic building block of the composition that has no children. It implements the base interface operations directly.
- Composite: A complex element that contains children (leaves or other composites). It implements the base interface by delegating work to its children.
- Recursive Composition: The ability for a container to hold other containers, enabling the creation of deeply nested, tree-like structures.
π The Engineering Story & Problem
π‘ The Villain (The Problem)
The "Nested Loop Nightmare" β a codebase riddled with if (isinstance(node, Department)) checks and deeply nested loops. Adding a new level to the hierarchy requires rewriting the traversal logic everywhere.
π¦Έ The Hero (The Solution)
The "Unified Node" β the Composite Pattern, which mandates that a single employee and an entire 500-person division must speak the same language (interface). The Department.get_salary() method simply iterates through its list and calls entity.get_salary() on each, regardless of whether it's an employee or a sub-department.
π Requirements & Constraints
- (Functional): Both
EmployeeandDepartmentmust implement a shared interface (OrganizationEntity). - (Functional): Calculating the salary of a
Departmentmust automatically include all sub-entities (transparent calculation). - (Technical): A
Departmentshould be able to contain anyEntity, whether it's aDeveloper(leaf) or anotherDepartment(composite). - (Technical): Calling
get_salary()on the root node should automatically trigger a recursive traversal of the entire tree.
ποΈ Structure & Blueprint
Class Diagram
classDiagram
direction TB
note "Composite"
class Component {
<<interface>>
+get_salary(): int
}
class Leaf {
-name: String
-salary: int
+get_salary(): int
}
class Composite {
-children: List~Component~
+add(Component component): void
+remove(Component component): void
+get_salary(): int
}
Component <|.. Leaf : Implements
Component <|.. Composite : Implements
Composite *-- Component : Contains
Runtime Context (Sequence)
sequenceDiagram
participant Client
participant Engineering as Engineering Dept
participant Backend as Backend Team
participant Dev as Developer (Leaf)
Client->>Engineering: get_salary()
Engineering->>Backend: get_salary()
Backend->>Dev: get_salary()
Dev-->>Backend: 80000
Backend-->>Engineering: 240000
Engineering-->>Client: 500000
π» Implementation & Code
π§ SOLID Principles Applied
- Single Responsibility: Each node manages its own state; leaves return their value, composites aggregate their children.
- Open/Closed: Adding a new component type (e.g.,
Contractor) requires no changes to the traversal logic.
π The Code
The Villain's Code (Without Pattern)
class OrgCalculator:
def total_salary(self, org):
total = 0
for dept in org.departments:
# π‘ Type-checking nightmare
if isinstance(dept, Department):
for team in dept.teams:
if isinstance(team, Team):
for emp in team.employees:
total += emp.salary
else:
total += team.salary
else:
total += dept.salary
return total
# Adding a new hierarchy level requires rewriting everything!
The Hero's Code (With Pattern)
from abc import ABC, abstractmethod
class Entity(ABC):
@abstractmethod
def get_salary(self) -> int:
pass
class Employee(Entity):
def __init__(self, name: str, salary: int) -> None:
self.name : str = name
self.salary : int = salary
def get_salary(self) -> int:
return self.salary
class Department(Entity):
def __init__(self, name: str) -> None:
self.name : str = name
self.members : list[Entity] = []
def add(self, entity: Entity) -> None:
self.members.append(entity)
def remove(self, entity: Entity) -> None:
if entity in self.members:
self.members.remove(entity)
def get_salary(self) -> int:
salary = 0
for member in self.members:
salary += member.get_salary()
return salary
e1 = Employee(name="A", salary=2)
e2 = Employee(name="B", salary=4)
d1 = Department(name="D")
d1.add(entity=e1)
d2 = Department(name="E")
d2.add(entity=e2)
d2.add(entity=d1)
print(d2.get_salary())
βοΈ Trade-offs & Testing
| Pros (Why it works) | Cons (The Twist / Pitfalls) |
|---|---|
Uniformity: Clients treat complex trees and simple leaves strictly identically without if-else checks. |
Over-Generalization: Makes it hard to restrict certain container types (e.g., "Only Folders can contain Folders"). |
| Easy Expansion: Adding a new component type into the composition tree is trivial. | Infinite Recursion: Risk of circular references (e.g., A contains B, B contains A) breaking traversal logic. |
| Clean Traversal: Eliminates type-checking and nested loops from business logic. | Bloat: Base interface might have methods (like add()) that are irrelevant for leaf nodes. |
π§ͺ Testing Strategy
Start from the bottom: Test an individual Leaf first. Then, test a Composite containing multiple leaves. Finally, test a Composite containing another Composite to ensure the recursive aggregation smoothly propagates up the tree.
π€ Interview Toolkit
- Interview Signal: Demonstrates a developer's ability to handle recursive data structures and their commitment to the "Open/Closed Principle"βthe system is open for expansion (new component types) but closed for modification (traversal logic stays the same).
- When to Use:
- When you need to represent part-whole hierarchies of objects.
- When you want clients to be able to ignore the difference between compositions of objects and individual objects.
- When the structure can be arbitrarily deep (file systems, UI trees, org charts).
- Scalability Probe: How would you handle a tree with 1 million nodes? (Answer: Use memoization/caching of results at each composite node, invalidating the cache only when a child is added/removed).
- Design Alternatives:
- Transparency vs Safety: Putting management methods (
add/remove) in Component provides Transparency but loses Type Safety. Keeping them in Composite provides Safety but requires the client to cast.
- Transparency vs Safety: Putting management methods (
π Related Patterns
- Decorator β Both have similar recursive structures, but Decorator has only one child.
- Visitor β Visitor can be used to apply an operation over a Composite tree without changing the node classes.
- Chain of Responsibility β Often used in conjunction with Composite where a component passes a request up to its parent.