YouTip LogoYouTip

Python Composite

The Composite Pattern is a structural design pattern that allows you to compose objects into tree structures to represent "part-whole" hierarchies. The Composite Pattern lets clients treat individual objects and object compositions uniformly, without caring whether they are dealing with a single object or an entire composite structure.

Real-life Analogy

Imagine the organizational structure of a file system:

  • Individual file: The most basic unit
  • Folder: Can contain multiple files or other folders
  • Whether you are operating on a file or a folder, you can perform operations such as "open", "delete", "rename", etc.

This "part-whole" hierarchical relationship is a typical application scenario of the Composite Pattern.


Core Components of the Composite Pattern

1. Component Interface

This is the core of the Composite Pattern, defining the common interface for all objects (including leaf nodes and composite nodes).

Example

from abc import ABC, abstractmethod

from typing import List

class FileSystemComponent(ABC):
    """File system component abstract base class"""

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

    @abstractmethod
    def display(self, indent: int = 0) -> None:
        """Display component information"""
        pass

    @abstractmethod
    def get_size(self) -> int:
        """Get component size"""
        pass

    def get_path(self) -> str:
        """Get full path"""
        if self.parent:
            return f"{self.parent.get_path()}/{self.name}"
        return self.name

2. Leaf Node

Represents a leaf object in the composition; leaf nodes have no child nodes.

Example

class File(FileSystemComponent):
    """File class - leaf node"""

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

    def display(self, indent: int = 0) -> None:
        """Display file information"""
        spaces = " " * indent
        print(f"{spaces}📄 {self.name} ({self._size} bytes)")

    def get_size(self) -> int:
        """Return file size"""
        return self._size

3. Composite Node

Represents a composite object that can contain sub-components, defining methods for storing sub-components.

Example

class Directory(FileSystemComponent):
    """Directory class - composite node"""

    def __init__(self, name: str):
        super().__init__(name)
        self._children: List = []

    def add(self, component: FileSystemComponent) -> None:
        """Add sub-component"""
        component.parent = self
        self._children.append(component)

    def remove(self, component: FileSystemComponent) -> None:
        """Remove sub-component"""
        self._children.remove(component)
        component.parent = None

    def display(self, indent: int = 0) -> None:
        """Display directory and all its sub-components"""
        spaces = " " * indent
        print(f"{spaces}📁 {self.name}/")

        # Recursively display all sub-components
        for child in self._children:
            child.display(indent + 1)

    def get_size(self) -> int:
        """Calculate total directory size (including all sub-components)"""
        total_size = 0
        for child in self._children:
            total_size += child.get_size()
        return total_size

    def find_component(self, name: str) -> FileSystemComponent:
        """Find component by specified name"""
        for child in self._children:
            if child.name == name:
                return child
            if isinstance(child, Directory):
                found = child.find_component(name)
                if found:
                    return found
        return None

Complete Example: File System Simulation

Let's demonstrate the practical application of the Composite Pattern through a complete example:

Example

def demonstrate_composite_pattern():
    """Demonstrate the use of Composite Pattern"""
    # Create root directory
    root = Directory("root")

    # Create subdirectories
    documents = Directory("documents")
    pictures = Directory("pictures")
    music = Directory("music")

    # Create files
    readme = File("README.txt", 1024)
    notes = File("notes.md", 2048)
    photo1 = File("vacation.jpg", 1536000)
    photo2 = File("family.png", 2048000)
    song1 = File("song1.mp3", 4096000)
    song2 = File("song2.mp3", 5120000)

    # Build directory structure
    root.add(readme)
    root.add(documents)
    root.add(pictures)
    root.add(music)

    documents.add(notes)
    pictures.add(photo1)
    pictures.add(photo2)
    music.add(song1)
    music.add(song2)

    # Display entire file system structure
    print("=== File System Structure ===")
    root.display()

    print("n=== Size Statistics ===")
    print(f"Root directory total size: {root.get_size()} bytes")
    print(f"Pictures directory size: {pictures.get_size()} bytes")
    print(f"Music directory size: {music.get_size()} bytes")

    print("n=== Path Information ===")
    print(f"File path: {photo1.get_path()}")
    print(f"Directory path: {pictures.get_path()}")

    print("n=== Find Component ===")
    found = root.find_component("song1.mp3")
    if found:
        print(f"Found file: {found.get_path()}")

# Run demonstration
if __name__ == "__main__":
    demonstrate_composite_pattern()

Running the above code, you will see the following output:

Image 1


UML Class Diagram of Composite Pattern

Image 2


Advantages and Disadvantages of Composite Pattern

Advantages

  1. Unified interface: Clients can use individual objects and composite objects consistently
  2. Open/Closed Principle: Easy to add new types of components without modifying existing code
  3. Simplified client code: Clients don't need to determine whether they are dealing with an individual object or a composite
  4. Flexible hierarchical structure: Can build complex tree structures

Disadvantages

  1. Design complexity: Overly generalized design may make the system complex
  2. Type safety issues: In some cases, runtime type checking may be required
  3. Performance considerations: For large hierarchies, recursive operations may affect performance

Practical Application Scenarios

1. GUI Component System

Example

class UIComponent:
    """UI component base class"""

    def render(self):
        pass

    def add(self, component):
        pass

class Button(UIComponent):
    """Button - leaf node"""

    def render(self):
        print("Render button")

class Panel(UIComponent):
    """Panel - composite node"""

    def __init__(self):
        self.children = []

    def add(self, component):
        self.children.append(component)

    def render(self):
        print("Start rendering panel")
        for child in self.children:
            child.render()
        print("Finish rendering panel")

2. Organizational Structure Management

Example

class Employee:
    """Employee base class"""

    def get_salary(self):
        pass

class Developer(Employee):
    """Developer - leaf node"""

    def __init__(self, salary):
        self.salary = salary

    def get_salary(self):
        return self.salary

class Department(Employee):
    """Department - composite node"""

    def __init__(self):
        self.employees = []

    def add(self, employee):
        self.employees.append(employee)

    def get_salary(self):
        return sum(emp.get_salary() for emp in self.employees)

Practice Exercises

Exercise 1: Extend File System

Add the following features to the file system:

  1. Implement file copy functionality
  2. Add file type filtering search
  3. Implement directory depth-limited display

Exercise 2: Menu System Design

Design a restaurant menu system with the following requirements:

  • Menu can contain dishes or sub-menus
  • Ability to calculate total price
  • Support filtering dishes by category

Exercise 3: Organizational Structure Chart

Create a company organizational structure system:

  • Employees can be individuals or departments
  • Departments can contain other employees or sub-departments
  • Ability to calculate department total headcount and total salary

Summary

The Composite Pattern handles individual objects and object compositions through a unified interface, making client code more concise and flexible. This pattern is particularly suitable for scenarios with hierarchical structures, such as file systems, GUI components, organizational structures, etc.

Key Points:

  • The component interface defines common operations for all objects
  • Leaf nodes implement basic functionality
  • Composite nodes manage sub-components and provide aggregate operations
  • Clients do not need to distinguish between individual objects and object compositions
← Python ObserverPython Facade β†’