Skip to content

Composition Pattern

Strong Has-a

1. Definition

Composition is a strong form of association where:

  • One class owns instances of another class
  • The lifetime of the component is tied to the container
  • When the container is destroyed, components are destroyed
  • Models whole-part relationships

2. Ownership Model

The container creates and controls its components:

class Engine:
    def start(self):
        return "Engine running"

class Car:
    def __init__(self):
        self.engine = Engine()  # Created here, owned by Car

    def start(self):
        return self.engine.start()

car = Car()  # Creates Engine
del car      # Destroys Engine too

3. Key Principle

Components cannot exist meaningfully outside their container.

Implementation

1. Basic Pattern

class Heart:
    def beat(self):
        return "Beating"

class Brain:
    def think(self):
        return "Thinking"

class Human:
    def __init__(self, name):
        self.name = name
        self.heart = Heart()    # Composed
        self.brain = Brain()    # Composed

    def live(self):
        return f"{self.heart.beat()}, {self.brain.think()}"

person = Human("Alice")
print(person.live())  # Beating, Thinking

2. Multiple Components

class CPU:
    def __init__(self, cores):
        self.cores = cores

    def process(self):
        return f"Processing on {self.cores} cores"

class RAM:
    def __init__(self, size):
        self.size = size

    def store(self):
        return f"Storing in {self.size}GB RAM"

class Computer:
    def __init__(self, cpu_cores, ram_size):
        self.cpu = CPU(cpu_cores)      # Composed
        self.ram = RAM(ram_size)       # Composed
        self.storage = []              # Composed

    def specs(self):
        return f"{self.cpu.process()}, {self.ram.store()}"

pc = Computer(8, 16)
print(pc.specs())  # Processing on 8 cores, Storing in 16GB RAM

3. Nested Composition

class Wheel:
    def __init__(self, size):
        self.size = size

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

class Chassis:
    def __init__(self):
        self.wheels = [Wheel(18) for _ in range(4)]

class Car:
    def __init__(self):
        self.chassis = Chassis()     # Composed
        self.engine = Engine(200)    # Composed

Benefits

1. Modularity

Break complex systems into manageable parts:

class DisplayController:
    def render(self):
        return "Rendering display"

class InputController:
    def handle_input(self):
        return "Handling input"

class AudioController:
    def play_sound(self):
        return "Playing sound"

class GameConsole:
    def __init__(self):
        self.display = DisplayController()
        self.input = InputController()
        self.audio = AudioController()

    def run(self):
        return f"{self.display.render()}, {self.input.handle_input()}"

2. Encapsulation

Hide implementation details:

class Database:
    def query(self, sql):
        # Complex database logic
        return "Query result"

class Cache:
    def get(self, key):
        return "Cached value"

class DataService:
    def __init__(self):
        self._database = Database()  # Hidden implementation
        self._cache = Cache()        # Hidden implementation

    def get_data(self, id):
        # Client doesn't know about DB or Cache
        cached = self._cache.get(id)
        if cached:
            return cached
        return self._database.query(f"SELECT * FROM table WHERE id={id}")

3. Flexibility

Swap implementations without changing interface:

class EmailSender:
    def send(self, message):
        return f"Email sent: {message}"

class SMSSender:
    def send(self, message):
        return f"SMS sent: {message}"

class NotificationService:
    def __init__(self, sender):
        self.sender = sender

    def notify(self, message):
        return self.sender.send(message)

# Flexible composition
email_service = NotificationService(EmailSender())
sms_service = NotificationService(SMSSender())

Lifetime Control

1. Container Controls

The container manages component lifecycle:

class Connection:
    def __init__(self):
        print("Connection opened")

    def close(self):
        print("Connection closed")

class DatabaseSession:
    def __init__(self):
        self.connection = Connection()  # Created

    def __del__(self):
        self.connection.close()  # Destroyed with container

session = DatabaseSession()  # Connection opened
del session                  # Connection closed

2. Initialization Order

Components initialize in order:

class Logger:
    def __init__(self):
        print("Logger initialized")

class Config:
    def __init__(self):
        print("Config initialized")

class Application:
    def __init__(self):
        self.config = Config()    # First
        self.logger = Logger()    # Second
        print("Application ready")

app = Application()
# Output:
# Config initialized
# Logger initialized
# Application ready

3. Cleanup Guarantee

Container ensures cleanup:

class TempFile:
    def __init__(self, name):
        self.name = name
        print(f"Created {name}")

    def cleanup(self):
        print(f"Deleted {name}")

class FileProcessor:
    def __init__(self):
        self.temp_files = [TempFile(f"temp_{i}") for i in range(3)]

    def __del__(self):
        for f in self.temp_files:
            f.cleanup()

processor = FileProcessor()
# Created temp_0, temp_1, temp_2
del processor
# Deleted temp_0, temp_1, temp_2

Design Patterns

1. Builder Pattern

class Burger:
    def __init__(self):
        self.bun = None
        self.patty = None
        self.toppings = []

class BurgerBuilder:
    def __init__(self):
        self.burger = Burger()

    def add_bun(self, bun_type):
        self.burger.bun = bun_type
        return self

    def add_patty(self, patty_type):
        self.burger.patty = patty_type
        return self

    def add_topping(self, topping):
        self.burger.toppings.append(topping)
        return self

    def build(self):
        return self.burger

burger = (BurgerBuilder()
    .add_bun("sesame")
    .add_patty("beef")
    .add_topping("lettuce")
    .add_topping("tomato")
    .build())

2. Facade Pattern

class VideoFile:
    def load(self):
        return "Video loaded"

class AudioFile:
    def load(self):
        return "Audio loaded"

class Codec:
    def decode(self):
        return "Decoded"

class MediaPlayer:
    """Facade that composes subsystems"""
    def __init__(self):
        self.video = VideoFile()
        self.audio = AudioFile()
        self.codec = Codec()

    def play(self, filename):
        video = self.video.load()
        audio = self.audio.load()
        decoded = self.codec.decode()
        return f"{video}, {audio}, {decoded}"

player = MediaPlayer()
player.play("movie.mp4")  # Simple interface, complex composition

3. Composite Pattern

class File:
    def __init__(self, name, size):
        self.name = name
        self.size = size

    def get_size(self):
        return self.size

class Directory:
    def __init__(self, name):
        self.name = name
        self.children = []

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

    def get_size(self):
        return sum(child.get_size() for child in self.children)

root = Directory("root")
root.add(File("file1.txt", 100))
root.add(File("file2.txt", 200))

subdir = Directory("subdir")
subdir.add(File("file3.txt", 150))
root.add(subdir)

print(root.get_size())  # 450

Common Use Cases

1. Document Structure

class Paragraph:
    def __init__(self, text):
        self.text = text

class Image:
    def __init__(self, url):
        self.url = url

class Section:
    def __init__(self, title):
        self.title = title
        self.paragraphs = []
        self.images = []

class Document:
    def __init__(self, title):
        self.title = title
        self.sections = []

    def add_section(self, section):
        self.sections.append(section)

2. UI Components

class Label:
    def __init__(self, text):
        self.text = text

class Button:
    def __init__(self, label):
        self.label = label

class TextInput:
    def __init__(self, placeholder):
        self.placeholder = placeholder

class Form:
    def __init__(self):
        self.inputs = []
        self.buttons = []
        self.labels = []

    def add_input(self, input_field):
        self.inputs.append(input_field)

3. Game Objects

class Position:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Sprite:
    def __init__(self, image):
        self.image = image

class Collider:
    def __init__(self, width, height):
        self.width = width
        self.height = height

class GameObject:
    def __init__(self):
        self.position = Position(0, 0)
        self.sprite = Sprite("default.png")
        self.collider = Collider(32, 32)

Best Practices

1. Single Responsibility

Each component has one job:

class Validator:
    def validate(self, data):
        return True

class Parser:
    def parse(self, text):
        return {"parsed": text}

class Processor:
    def __init__(self):
        self.validator = Validator()
        self.parser = Parser()

    def process(self, text):
        if self.validator.validate(text):
            return self.parser.parse(text)

2. Dependency Injection

Pass dependencies rather than create:

# ✅ GOOD - Flexible
class Service:
    def __init__(self, logger, database):
        self.logger = logger
        self.database = database

# ❌ BAD - Rigid
class Service:
    def __init__(self):
        self.logger = Logger()      # Hard-coded
        self.database = Database()  # Hard-coded

3. Interface Segregation

Depend on abstractions:

from abc import ABC, abstractmethod

class Storage(ABC):
    @abstractmethod
    def save(self, data):
        pass

class FileStorage(Storage):
    def save(self, data):
        # Save to file
        pass

class DataManager:
    def __init__(self, storage: Storage):
        self.storage = storage  # Depends on interface

Testing

1. Easy Mocking

Composition enables easy testing:

class MockDatabase:
    def query(self, sql):
        return {"mock": "data"}

class DataService:
    def __init__(self, database):
        self.database = database

    def get_user(self, id):
        return self.database.query(f"SELECT * FROM users WHERE id={id}")

# Easy to test
service = DataService(MockDatabase())
assert service.get_user(1) == {"mock": "data"}

2. Isolated Testing

Test components independently:

class Calculator:
    def add(self, a, b):
        return a + b

class Display:
    def show(self, value):
        return f"Result: {value}"

class CalculatorApp:
    def __init__(self):
        self.calc = Calculator()
        self.display = Display()

# Test Calculator alone
assert Calculator().add(2, 3) == 5

# Test Display alone
assert Display().show(5) == "Result: 5"

3. Integration Testing

Test composed system:

app = CalculatorApp()
result = app.calc.add(2, 3)
output = app.display.show(result)
assert output == "Result: 5"