← Назад к вопросам
Какая возникнет проблема при неправильном использовании композиции?
3.0 Senior🔥 151 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблемы при неправильном использовании композиции
Композиция — мощный инструмент для организации кода, но при неправильном применении приводит к проблемам архитектуры, тестирования и поддерживаемости. Рассмотрим основные подводные камни.
1. Циклические зависимости
Когда объект A содержит B, а B содержит A, возникает циклическая зависимость:
# ❌ Проблема: циклическая зависимость
class Parent:
def __init__(self):
self.child = Child(self) # передаём себя
class Child:
def __init__(self, parent):
self.parent = parent # содержим родителя
parent = Parent()
print(parent.child.parent is parent) # True — но проблема для тестирования
# ✅ Решение: инверсия управления
class Parent:
def __init__(self):
self.child = None
def attach_child(self, child):
self.child = child
child.parent = self
class Child:
def __init__(self):
self.parent = None
parent = Parent()
child = Child()
parent.attach_child(child)
2. Глубокая цепочка зависимостей
Если объект зависит от другого, который зависит от третьего и т.д., код становится хрупким:
# ❌ Проблема: глубокая цепь зависимостей
class Database:
def query(self, sql): pass
class Repository:
def __init__(self, db: Database):
self.db = db
def find_user(self, id):
return self.db.query(f"SELECT * FROM users WHERE id={id}")
class UserService:
def __init__(self, repo: Repository):
self.repo = repo
def get_user(self, id):
return self.repo.find_user(id)
class UserController:
def __init__(self, service: UserService):
self.service = service
def get_user_route(self, id):
return self.service.get_user(id)
# При тестировании нужно мокировать всю цепь!
from unittest.mock import Mock
db_mock = Mock()
repo_mock = Mock()
service_mock = Mock()
controller = UserController(service_mock)
# ✅ Решение: инъекция зависимостей с контейнером
from dataclasses import dataclass
@dataclass
class Container:
db: Database
repo: Repository
service: UserService
controller: UserController
container = Container(
db=Database(),
repo=Repository(Database()),
service=UserService(Repository(Database())),
controller=None
)
3. Нарушение принципа единственной ответственности
Когда объект отвечает за слишком много, композиция усугубляет проблему:
# ❌ Проблема: слишком много ответственности
class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.db = Database() # не должно быть здесь!
self.email_service = EmailService() # и это!
self.logger = Logger() # и это!
def save(self):
self.db.save(self)
self.logger.info(f"User {self.name} saved")
def send_email(self, message):
self.email_service.send(self.email, message)
self.logger.info(f"Email sent to {self.email}")
# ✅ Решение: разделение ответственности
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
class UserService:
def __init__(self, user_repo: UserRepository, email_service: EmailService, logger: Logger):
self.repo = user_repo
self.email_service = email_service
self.logger = logger
def save_user(self, user: User):
self.repo.save(user)
self.logger.info(f"User {user.name} saved")
def send_email(self, user: User, message: str):
self.email_service.send(user.email, message)
self.logger.info(f"Email sent to {user.email}")
4. Проблема с тестированием: Fragile Tests
Глубокая композиция делает тесты хрупкими:
# ❌ Проблема: тесты зависят от реализации
class OrderProcessor:
def __init__(self):
self.payment = PaymentGateway()
self.inventory = InventoryService()
self.notification = NotificationService()
self.logger = Logger()
def process(self, order):
# много логики
pass
# Тест ломается при любом изменении конструктора
def test_order_processing():
processor = OrderProcessor() # нужно создать весь граф
# ✅ Решение: инъекция зависимостей
class OrderProcessor:
def __init__(self, payment, inventory, notification, logger):
self.payment = payment
self.inventory = inventory
self.notification = notification
self.logger = logger
def test_order_processing():
processor = OrderProcessor(
payment=Mock(),
inventory=Mock(),
notification=Mock(),
logger=Mock()
)
5. Утечки памяти из-за циклических ссылок
# ❌ Проблема: утечка памяти
class Node:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = self # циклическая ссылка!
root = Node("root")
child = Node("child")
root.add_child(child)
# Даже если удалим root, child.parent ссылается на root
# Сборщик мусора может не освободить память сразу
# ✅ Решение: использовать weakref
import weakref
class Node:
def __init__(self, value):
self.value = value
self.parent = None # будет weakref
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = weakref.ref(self) # слабая ссылка
def get_parent(self):
return self.parent() if self.parent else None
6. Hidden Dependencies (скрытые зависимости)
# ❌ Проблема: зависимость скрыта внутри
class EmailService:
def __init__(self):
self.config = Config() # откуда это берётся?
self.smtp = SMTPClient(self.config.smtp_host)
def send(self, to, message):
self.smtp.send(to, message)
# При тестировании сложно мокировать
# ✅ Решение: явные зависимости
class EmailService:
def __init__(self, config: Config, smtp: SMTPClient):
self.config = config
self.smtp = smtp
def send(self, to: str, message: str):
self.smtp.send(to, message)
7. Проблема с изменяемыми объектами по умолчанию
# ❌ Проблема: изменяемый аргумент по умолчанию
class User:
def __init__(self, name: str, tags: list = []):
self.name = name
self.tags = tags
user1 = User("Alice")
user1.tags.append("admin")
user2 = User("Bob")
print(user2.tags) # [admin] — ошибка!
# ✅ Решение: None и создание нового объекта
class User:
def __init__(self, name: str, tags: list = None):
self.name = name
self.tags = tags if tags is not None else []
8. Нарушение инкапсуляции
# ❌ Проблема: внутренние детали видны снаружи
class BankAccount:
def __init__(self, balance: float):
self.balance = balance # public!
self.logger = Logger()
def deposit(self, amount):
self.balance += amount
self.logger.log(f"Deposited {amount}")
account = BankAccount(100)
account.balance = -1000 # ломаем инвариант!
# ✅ Решение: приватные атрибуты
class BankAccount:
def __init__(self, balance: float):
self._balance = balance
self._logger = Logger()
def deposit(self, amount: float):
if amount <= 0:
raise ValueError("Amount must be positive")
self._balance += amount
self._logger.log(f"Deposited {amount}")
@property
def balance(self) -> float:
return self._balance
Итоги
Основные проблемы неправильной композиции:
- Циклические зависимости и утечки памяти
- Глубокие цепочки зависимостей
- Нарушение единственной ответственности
- Сложность тестирования (Fragile Tests)
- Скрытые зависимости
- Нарушение инкапсуляции
Правила для правильной композиции:
- Используй инъекцию зависимостей
- Избегай циклических ссылок (используй weakref если необходимо)
- Передавай зависимости явно в конструктор
- Ограничивай глубину цепи (не более 3-4 уровней)
- Следуй принципу единственной ответственности
- Скрывай внутренние детали (инкапсуляция)