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

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

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

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Когда стоит использовать композицию в объектно-ориентированном программировании?

Композиция — это принцип проектирования, при котором один объект содержит или состоит из других объектов, делегируя им часть своей функциональности. В отличие от наследования, которое создаёт отношение «является» (is-a), композиция определяет отношение «имеет» (has-a). Использование композиции предпочтительно в большинстве случаев, так как она обеспечивает большую гибкость, слабую связанность и лучше соответствует принципам SOLID.

Ключевые ситуации для выбора композиции

1. Для избежания проблем хрупкого базового класса

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

// Проблема с наследованием
open class Vehicle {
    open fun startEngine() { /* общая логика */ }
}

class Car : Vehicle() {
    // Переопределение может сломаться при изменении Vehicle
}

// Решение через композицию
class Engine {
    fun start() { /* логика запуска */ }
}

class Car {
    private val engine = Engine()
    fun start() = engine.start()
}

2. Для реализации поведения «имеет» вместо «является»

Когда объект должен владеть или использовать другой объект, а не быть его специализацией.

// Неправильно через наследование
class AndroidDeveloper : Human(), CodeWriter, Debugger { } // Нарушает ISP

// Правильно через композицию
class AndroidDeveloper {
    private val coder: CodeWriter = SeniorCoder()
    private val debugger: Debugger = SystemOutDebugger()
    
    fun developFeature() {
        coder.writeCode()
        debugger.findBugs()
    }
}

3. Для динамического изменения поведения во время выполнения

Композиция позволяет менять поведение объекта «на лету», что невозможно при наследовании.

interface NotificationSender {
    fun send(message: String)
}

class Developer {
    private var notifier: NotificationSender
    
    fun setNotifier(notifier: NotificationSender) {
        this.notifier = notifier
    }
    
    fun notifyTeam(msg: String) {
        notifier.send(msg) // Может быть Email, Slack, Push и т.д.
    }
}

4. Для соблюдения принципа единственной ответственности (SRP)

Класс должен иметь одну причину для изменения. Композиция помогает разделять ответственности.

// Вместо одного «божественного» класса
class GodFragment {
    // Управление UI, работа с сетью, кэширование, логика...
}

// Разделяем через композицию
class ProductFragment {
    private val uiManager: UIManager
    private val apiClient: ApiClient
    private val cache: MemoryCache
    private val analytics: AnalyticsTracker
    // Каждый компонент отвечает за свою область
}

5. Для тестирования и мокинга зависимостей

Композиция упрощает юнит-тестирование, позволяя подменять реальные зависимости заглушками.

class UserRepository(
    private val api: UserApi, // Внедряем через конструктор
    private val cache: UserCache
) {
    fun getUser(id: String): User {
        return cache.get(id) ?: api.fetchUser(id).also { cache.save(it) }
    }
}

// В тесте легко подменить реальные реализации
@Test
fun testUserRepository() {
    val mockApi = mockk<UserApi>()
    val fakeCache = FakeUserCache()
    val repo = UserRepository(mockApi, fakeCache)
    // Тестируем изолированно
}

6. Для реализации полиморфизма без наследования

Через интерфейсы и композицию можно достичь полиморфного поведения без жесткой иерархии.

interface Renderer {
    fun draw(view: View)
}

class CanvasRenderer : Renderer { /* реализация */ }
class SVGRenderer : Renderer { /* другая реализация */ }

class ChartComponent(private val renderer: Renderer) {
    fun display() {
        renderer.draw(this) // Полиморфное поведение
    }
}

Практические рекомендации для Android-разработки

  1. Используйте композицию в ViewModel и UseCase — вместо наследования, внедряйте репозитории, источники данных.
  2. Предпочитайте делегирование в Fragment/Activity — выносите логику в отдельные классы (навигация, проверка прав).
  3. Применяйте принцип «композиция над наследованием» в дизайн-системах — создавайте UI-компоненты из более мелких.
  4. Используйте композицию для реализации Feature Toggles — динамическое включение/выключение функциональности.

Исключения: когда наследование уместно

  1. Создание семейств связанных классов с общей базовой логикой (например, кастомные View).
  2. Переиспользование кода в рамках одного модуля при гарантии стабильности базового класса.
  3. Расширение классов фреймворка (Activity, Fragment), когда это предусмотрено архитектурой.

Заключение

Композицию следует использовать по умолчанию в большинстве сценариев проектирования. Она обеспечивает:

  • Гибкость архитектуры
  • Упрощение тестирования
  • Соблюдение SOLID-принципов
  • Устойчивость к изменениям

Правило «Favor composition over inheritance» (предпочитайте композицию наследованию) остается одним из фундаментальных принципов создания поддерживаемого и масштабируемого кода в Android-разработке. Начинайте с композиции и переходите к наследованию только когда есть четкое отношение «является» и вы уверены в стабильности иерархии.

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