Что такое композиция в контексте ООП?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое композиция в контексте ООП?
Композиция — это один из основных механизмов переиспользования кода в объектно-ориентированном программировании. Вместо наследования класс содержит объекты других классов и делегирует им часть работы. Правило: "использовать композицию вместо наследования".
Композиция 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", а для всего остального используй композицию. Композиция даёт больше гибкости, проще тестировать и легче менять.
Композиция — это один из самых важных принципов чистого кода, потому что она позволяет создавать простые, гибкие и тестируемые системы.