Сравните принципы композиции и наследования (Composition vs Inheritance).android-221

Основные концепции

Наследование

"Является" (is-a) отношение:

class Button extends View {
    // Button это View
}

Композиция

"Имеет" (has-a) отношение:

class CustomView {
    private Button button; // CustomView содержит Button
}

Сравнительная таблица

Критерий Наследование Композиция
ГибкостьЖесткая связьГибкая связь
Повторное использованиеБесплатное (все методы родителя)Требует явного делегирования
ТестируемостьСложнее (зависимость от родителя)Проще (можно мокировать компоненты)
ИзменяемостьТребует изменения иерархииЛегко заменить компоненты
Количество классовМеньше (но сложнее)Больше (но проще)
Доступ к полямПрямой доступ к protectedЧеткие интерфейсы

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

  1. Fragile Base Class проблема:

    • Изменения в родительском классе ломают дочерние
    class Base {
        void foo() { /* изменение ломает наследников */ }
    }
    
  2. Нарушение инкапсуляции:

    • Дочерние классы знают детали реализации родителя
  3. Множественное наследование:

    • В Java запрещено (кроме интерфейсов)

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

  1. Следование SOLID:

    • Single Responsibility: Каждый класс отвечает за одно
    • Open-Closed: Легко расширять новыми компонентами
    • Dependency Inversion: Зависим от абстракций
  2. Пример в Android:

    class MyViewModel(
        private val repository: UserRepository, // Внедрение зависимости
        private val logger: ErrorLogger        // Композиция
    ) {
        fun loadData() {
            try {
                repository.getUsers()
            } catch (e: Exception) {
                logger.log(e) // Делегирование
            }
        }
    }
    

Когда использовать наследование

  1. Строгое отношение "является":

    • SaveToFileDialog действительно является Dialog
    • Rectangle действительно является Shape
  2. Неизменяемая базовая функциональность:

    • Когда базовый класс стабилен и не будет меняться
  3. Полиморфизм через переопределение:

    abstract class Animal {
        abstract void makeSound();
    }
    

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

  1. Отношение "содержит/использует":

    • Car имеет Engine, но не является двигателем
  2. Изменяемое поведение:

    class PaymentProcessor {
        private PaymentStrategy strategy; // Можно менять
    }
    
  3. Разделение ответственностей:

    • Вместо одного большого класса-наследника

Современные практики в Android

  1. Предпочтение композиции:

    • ViewModel с репозиториями
    • Activity/Fragment с делегатами
  2. Использование интерфейсов:

    class MyClass(
        private val dependency: MyInterface // Композиция + абстракция
    ) : MyInterface by dependency          // Делегирование
    

Резюмируем:

В современной Android-разработке композиция предпочтительнее наследования в большинстве случаев, так как обеспечивает лучшую гибкость, тестируемость и соответствие принципам SOLID. Наследование следует использовать осознанно только для истинных отношений "является", когда полиморфизм действительно необходим.