Composite Pattern

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.


Problem

You need to work with hierarchical tree structures (files/folders, UI components, organizational charts) where clients need to treat individual objects and collections of objects the same way.

Solution

The Composite pattern represents part-whole hierarchies as tree structures composed of objects, all inheriting from a common component interface.


Implementation

from abc import ABC, abstractmethod
from typing import List, Optional

# ============ FILE SYSTEM EXAMPLE ============

class FileSystemComponent(ABC):
    @abstractmethod
    def get_size(self) -> int:
        pass

    @abstractmethod
    def display(self, indent: int = 0):
        pass

class File(FileSystemComponent):
    """Leaf node - represents individual file"""

    def __init__(self, name: str, size: int):
        self.name = name
        self.size = size

    def get_size(self) -> int:
        return self.size

    def display(self, indent: int = 0):
        print(" " * indent + f"📄 {self.name} ({self.size} KB)")

class Folder(FileSystemComponent):
    """Composite node - represents folder containing files/folders"""

    def __init__(self, name: str):
        self.name = name
        self.children: List[FileSystemComponent] = []

    def add_component(self, component: FileSystemComponent):
        self.children.append(component)

    def remove_component(self, component: FileSystemComponent):
        self.children.remove(component)

    def get_size(self) -> int:
        """Recursive size calculation"""
        total = 0
        for child in self.children:
            total += child.get_size()
        return total

    def display(self, indent: int = 0):
        print(" " * indent + f"📁 {self.name}/")
        for child in self.children:
            child.display(indent + 2)

# ============ GRAPHIC COMPONENTS EXAMPLE ============

class GraphicComponent(ABC):
    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def rotate(self, angle: int):
        pass

class SimpleGraphic(GraphicComponent):
    """Leaf - simple graphic (circle, rectangle, etc.)"""

    def __init__(self, name: str):
        self.name = name

    def draw(self):
        print(f"Drawing {self.name}")

    def rotate(self, angle: int):
        print(f"Rotating {self.name} by {angle} degrees")

class CompositeGraphic(GraphicComponent):
    """Composite - group of graphics"""

    def __init__(self, name: str):
        self.name = name
        self.components: List[GraphicComponent] = []

    def add_component(self, component: GraphicComponent):
        self.components.append(component)

    def remove_component(self, component: GraphicComponent):
        self.components.remove(component)

    def draw(self):
        print(f"Drawing composite group: {self.name}")
        for component in self.components:
            component.draw()

    def rotate(self, angle: int):
        print(f"Rotating composite group {self.name} by {angle} degrees")
        for component in self.components:
            component.rotate(angle)

# ============ ORGANIZATIONAL CHART EXAMPLE ============

class Employee(ABC):
    def __init__(self, name: str, position: str):
        self.name = name
        self.position = position

    @abstractmethod
    def get_salary(self) -> float:
        pass

    @abstractmethod
    def display(self, indent: int = 0):
        pass

class Manager(Employee):
    """Composite - can manage other employees"""

    def __init__(self, name: str, position: str, base_salary: float):
        super().__init__(name, position)
        self.base_salary = base_salary
        self.subordinates: List[Employee] = []

    def add_subordinate(self, employee: Employee):
        self.subordinates.append(employee)

    def remove_subordinate(self, employee: Employee):
        self.subordinates.remove(employee)

    def get_salary(self) -> float:
        """Manager salary + subordinates' salaries"""
        total = self.base_salary
        for subordinate in self.subordinates:
            total += subordinate.get_salary()
        return total

    def display(self, indent: int = 0):
        print(" " * indent + f"👔 Manager: {self.name} ({self.position}) - ${self.base_salary}")
        for subordinate in self.subordinates:
            subordinate.display(indent + 2)

class Developer(Employee):
    """Leaf - individual contributor"""

    def __init__(self, name: str, position: str, salary: float):
        super().__init__(name, position)
        self.salary = salary

    def get_salary(self) -> float:
        return self.salary

    def display(self, indent: int = 0):
        print(" " * indent + f"💻 Developer: {self.name} ({self.position}) - ${self.salary}")

class Designer(Employee):
    """Leaf - individual contributor"""

    def __init__(self, name: str, position: str, salary: float):
        super().__init__(name, position)
        self.salary = salary

    def get_salary(self) -> float:
        return self.salary

    def display(self, indent: int = 0):
        print(" " * indent + f"🎨 Designer: {self.name} ({self.position}) - ${self.salary}")

# ============ MENU SYSTEM EXAMPLE ============

class MenuItem(ABC):
    @abstractmethod
    def display(self, indent: int = 0):
        pass

class Item(MenuItem):
    """Leaf - actual menu item with action"""

    def __init__(self, name: str, description: str):
        self.name = name
        self.description = description

    def display(self, indent: int = 0):
        print(" " * indent + f"• {self.name}: {self.description}")

class Menu(MenuItem):
    """Composite - menu containing items/submenus"""

    def __init__(self, name: str):
        self.name = name
        self.items: List[MenuItem] = []

    def add_item(self, item: MenuItem):
        self.items.append(item)

    def remove_item(self, item: MenuItem):
        self.items.remove(item)

    def display(self, indent: int = 0):
        print(" " * indent + f"📋 {self.name}")
        for item in self.items:
            item.display(indent + 2)

# Demo
if __name__ == "__main__":
    print("=== File System ===")
    root = Folder("root")

    documents = Folder("Documents")
    pictures = Folder("Pictures")

    doc1 = File("resume.pdf", 50)
    doc2 = File("letter.docx", 30)
    pictures_file = File("vacation.jpg", 2000)

    documents.add_component(doc1)
    documents.add_component(doc2)
    pictures.add_component(pictures_file)

    root.add_component(documents)
    root.add_component(pictures)

    root.display()
    print(f"Total size: {root.get_size()} KB\n")

    print("=== Graphics ===")
    group = CompositeGraphic("Main Scene")
    circle = SimpleGraphic("Circle")
    rectangle = SimpleGraphic("Rectangle")
    group.add_component(circle)
    group.add_component(rectangle)

    group.draw()
    group.rotate(45)
    print()

    print("=== Organizational Chart ===")
    ceo = Manager("John CEO", "CEO", 200000)

    dev_manager = Manager("Alice", "Dev Manager", 120000)
    design_manager = Manager("Bob", "Design Manager", 110000)

    dev1 = Developer("Charlie", "Senior Dev", 100000)
    dev2 = Developer("Diana", "Junior Dev", 70000)
    designer1 = Designer("Eve", "UI Designer", 80000)

    dev_manager.add_subordinate(dev1)
    dev_manager.add_subordinate(dev2)
    design_manager.add_subordinate(designer1)

    ceo.add_subordinate(dev_manager)
    ceo.add_subordinate(design_manager)

    ceo.display()
    print(f"Total payroll: ${ceo.get_salary()}\n")

    print("=== Menu System ===")
    main_menu = Menu("Main Menu")

    file_menu = Menu("File")
    file_menu.add_item(Item("New", "Create new file"))
    file_menu.add_item(Item("Open", "Open file"))
    file_menu.add_item(Item("Save", "Save file"))

    edit_menu = Menu("Edit")
    edit_menu.add_item(Item("Undo", "Undo action"))
    edit_menu.add_item(Item("Redo", "Redo action"))

    main_menu.add_item(file_menu)
    main_menu.add_item(edit_menu)

    main_menu.display()

Key Concepts

  • Component: Common interface for leaf and composite objects
  • Leaf: Terminal objects with no children
  • Composite: Objects that can contain children
  • Uniformity: Treat individual and composite objects the same way
  • Recursive Composition: Composites can contain other composites

When to Use

✅ Hierarchical tree structures (file systems, UI components)
✅ Need to treat parts and wholes uniformly
✅ Want to represent part-whole hierarchies
✅ Client code shouldn't care about object complexity


Interview Q&A

Q1: What's the difference between Composite and Decorator?

A: - Composite: Tree structure, represents part-whole hierarchies - Decorator: Linear wrapping, adds functionality to individual objects

# Composite: Tree
root = Folder("root")
root.add_component(Folder("subfolder"))
root.add_component(File("file.txt", 100))

# Decorator: Linear
base_payment = PaymentProcessor()
logged = LoggingDecorator(base_payment)
validated = ValidationDecorator(logged)

Q2: How would you implement search in a composite structure?

A:

class FileSystemComponent(ABC):
    @abstractmethod
    def search(self, name: str) -> List['FileSystemComponent']:
        pass

class File(FileSystemComponent):
    def search(self, name: str):
        if name in self.name:
            return [self]
        return []

class Folder(FileSystemComponent):
    def search(self, name: str):
        results = []
        for child in self.children:
            results.extend(child.search(name))
        return results

Q3: How would you handle composite structure with operations on all nodes?

A:

class Node(ABC):
    @abstractmethod
    def operation(self):
        pass

class Composite(Node):
    def __init__(self):
        self.children = []

    def operation(self):
        # Operation on all children recursively
        for child in self.children:
            child.operation()

Q4: Can you have a composite that only accepts certain types of children?

A:

class TypeRestrictedComposite(FileSystemComponent):
    def add_component(self, component: FileSystemComponent):
        if not isinstance(component, File):
            raise TypeError("Only Files allowed")
        self.children.append(component)

Q5: How would you serialize/deserialize a composite structure?

A:

import json

class SerializableFolder:
    def to_dict(self):
        return {
            "type": "folder",
            "name": self.name,
            "children": [child.to_dict() for child in self.children]
        }

    @staticmethod
    def from_dict(data):
        folder = SerializableFolder(data["name"])
        for child_data in data["children"]:
            if child_data["type"] == "file":
                folder.add_component(SerializableFile.from_dict(child_data))
            else:
                folder.add_component(SerializableFolder.from_dict(child_data))
        return folder

Trade-offs

Pros: Simplifies tree handling, uniform treatment of parts/wholes, clean recursive code
Cons: Can make interface too general, may not fit all use cases


Real-World Examples

  • File systems (folders, files, symlinks)
  • DOM trees (HTML elements)
  • Graphics rendering (shapes, groups)
  • Organization charts (managers, employees)
  • Menu systems (menus, menu items)