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

Какая возникнет проблема при неправильном использовании композиции?

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 уровней)
  • Следуй принципу единственной ответственности
  • Скрывай внутренние детали (инкапсуляция)
Какая возникнет проблема при неправильном использовании композиции? | PrepBro