← Назад к вопросам

Что такое композиция в контексте ООП?

2.0 Middle🔥 141 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое композиция в контексте ООП?

Композиция — это один из основных механизмов переиспользования кода в объектно-ориентированном программировании. Вместо наследования класс содержит объекты других классов и делегирует им часть работы. Правило: "использовать композицию вместо наследования".

Композиция vs Наследование

Рассмотрим классический пример: классы Animal, Dog, Bird.

Проблема с наследованием:

# ❌ Наследование создаёт проблемы
class Animal:
    def eat(self):
        print('Eating')
    
    def move(self):
        print('Moving')

class Bird(Animal):
    def fly(self):
        print('Flying')

class Penguin(Bird):
    # Пингвин не может летать!
    # Но наследуется метод fly() - нарушение LSP (Liskov Substitution Principle)
    pass

class Dog(Animal):
    def bark(self):
        print('Woof')

# Проблемы:
# 1. Иерархия хрупкая - изменение Animal влияет на всех
# 2. Нарушение LSP - Penguin имеет fly(), но не может летать
# 3. Множественное наследование приводит к Diamond Problem
# 4. Тесно связанный код

Решение с композицией:

# ✅ Композиция - гибче и правильнее
class Movement:
    pass

class Walking(Movement):
    def move(self):
        print('Walking')

class Flying(Movement):
    def move(self):
        print('Flying')

class Swimming(Movement):
    def move(self):
        print('Swimming')

class Behavior:
    pass

class Eating(Behavior):
    def eat(self):
        print('Eating')

class Barking(Behavior):
    def bark(self):
        print('Woof')

class Animal:
    def __init__(self, movement: Movement, behavior: Behavior):
        self.movement = movement  # Композиция
        self.behavior = behavior   # Композиция
    
    def move(self):
        self.movement.move()
    
    def eat(self):
        self.behavior.eat()

# Создание объектов
dog = Animal(Walking(), Eating())
dog.move()  # Walking
dog.eat()   # Eating

bird = Animal(Flying(), Eating())
bird.move()  # Flying
bird.eat()   # Eating

penguin = Animal(Swimming(), Eating())
penguin.move()  # Swimming - пингвин не летает!

Основные типы композиции

1. Простая композиция (Has-a relationship)

Один объект содержит другой, но не отвечает за его жизненный цикл:

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

class Car:
    def __init__(self, engine: Engine):
        self.engine = engine  # Car "has an" Engine
    
    def start(self):
        return self.engine.start()

# Engine может существовать отдельно
engine = Engine()
car = Car(engine)
print(car.start())  # Engine started

# Engine может быть переиспользован
car2 = Car(engine)  # Тот же engine в разных машинах

2. Агрегация (множественная композиция)

Один объект содержит коллекцию других объектов:

class Player:
    def __init__(self, name: str):
        self.name = name
    
    def info(self):
        return f'Player: {self.name}'

class Team:
    def __init__(self, team_name: str):
        self.team_name = team_name
        self.players = []  # Агрегация - коллекция
    
    def add_player(self, player: Player):
        self.players.append(player)
    
    def show_roster(self):
        print(f'Team: {self.team_name}')
        for player in self.players:
            print(f'  - {player.info()}')

team = Team('Python Warriors')
team.add_player(Player('Alice'))
team.add_player(Player('Bob'))
team.show_roster()
# Team: Python Warriors
#   - Player: Alice
#   - Player: Bob

3. Сильная композиция (жизненный цикл)

Родитель отвечает за создание и удаление потомков:

from typing import List

class Wheel:
    def __init__(self):
        self.pressure = 100
    
    def info(self):
        return f'Wheel (pressure: {self.pressure})'

class Car:
    def __init__(self):
        self.wheels: List[Wheel] = [Wheel() for _ in range(4)]  # Сильная композиция
    
    def show_info(self):
        print(f'Car with {len(self.wheels)} wheels:')
        for i, wheel in enumerate(self.wheels):
            print(f'  Wheel {i+1}: {wheel.info()}')
    
    def __del__(self):
        # Когда машина удаляется, удаляются и колёса
        print('Car destroyed (wheels also removed)')

car = Car()
car.show_info()
del car  # Удаляет машину и все колёса

Практический пример: Система уведомлений

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

# Стратегии отправки
class NotificationStrategy(ABC):
    @abstractmethod
    def send(self, message: str, recipient: str) -> bool:
        pass

class EmailNotification(NotificationStrategy):
    def send(self, message: str, recipient: str) -> bool:
        print(f'Email to {recipient}: {message}')
        return True

class SMSNotification(NotificationStrategy):
    def send(self, message: str, recipient: str) -> bool:
        print(f'SMS to {recipient}: {message}')
        return True

class PushNotification(NotificationStrategy):
    def send(self, message: str, recipient: str) -> bool:
        print(f'Push to {recipient}: {message}')
        return True

# Логирование
class Logger:
    def log(self, message: str):
        print(f'[{datetime.now()}] {message}')

# Главный класс с композицией
class NotificationManager:
    def __init__(self, logger: Logger):
        self.strategies: List[NotificationStrategy] = []  # Композиция
        self.logger = logger  # Композиция
    
    def add_strategy(self, strategy: NotificationStrategy):
        self.strategies.append(strategy)
    
    def notify(self, message: str, recipient: str):
        self.logger.log(f'Sending notification to {recipient}')
        
        for strategy in self.strategies:
            try:
                strategy.send(message, recipient)
            except Exception as e:
                self.logger.log(f'Failed with {strategy.__class__.__name__}: {e}')

# Использование
logger = Logger()
manager = NotificationManager(logger)
manager.add_strategy(EmailNotification())
manager.add_strategy(SMSNotification())
manager.add_strategy(PushNotification())

manager.notify('Important update!', 'user@example.com')
# [2025-03-22 10:30:45] Sending notification to user@example.com
# Email to user@example.com: Important update!
# SMS to user@example.com: Important update!
# Push to user@example.com: Important update!

Преимущества композиции

Гибкость — легко менять поведение в runtime ✅ Переиспользование — один объект можно передать разным классам ✅ Слабая связанность — изменения в одном классе не влияют на другие ✅ Простота тестирования — легко мокировать зависимости ✅ Соответствие SOLID — особенно DIP (Dependency Inversion) ✅ Избежание Diamond Problem — нет множественного наследования

Когда использовать композицию

# ✅ Используй композицию если:
# - Нужна гибкость в поведении
# - Объект содержит несколько разных типов данных
# - Нужно переиспользовать компоненты в разных контекстах
class PaymentProcessor:
    def __init__(self, gateway: PaymentGateway, validator: CardValidator):
        self.gateway = gateway
        self.validator = validator

# ❌ Наследование если:
# - Есть отношение "is-a" (не часто)
# - Нужно переопределить все методы
class SpecializedList(list):
    def append(self, item):
        if item > 0:
            super().append(item)

Правило большого пальца

Композиция > Наследование

Используй наследование только для отношений типа "is-a", а для всего остального используй композицию. Композиция даёт больше гибкости, проще тестировать и легче менять.

Композиция — это один из самых важных принципов чистого кода, потому что она позволяет создавать простые, гибкие и тестируемые системы.

Что такое композиция в контексте ООП? | PrepBro