Перейти к содержанию

Паттерны проектирования

Паттерны проектирования - именованные подходы к повторяющимся задачам проектирования. Они дают общий словарь, но не являются готовым кодом или причиной добавлять абстракции до появления проблемы.

23 паттерна Gang of Four (GoF) обычно делят на группы:

  • Порождающие: Abstract Factory, Builder, Factory Method, Prototype, Singleton.
  • Структурные: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.
  • Поведенческие: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor.

Ниже рассмотрены паттерны, особенно часто встречающиеся в Android-коде и API.

Factory Method и Abstract Factory

Factory Method определяет операцию создания, которую наследники или реализации переопределяют для выбора конкретного продукта. Отдельную функцию вроде createParser(format), выбирающую класс, обычно называют простой фабрикой, а не Factory Method из GoF.

Abstract Factory создаёт семейство связанных продуктов через единый контракт. Она может предоставлять согласованный набор платформенных сервисов или UI-компонентов, не раскрывая конкретные классы.

Используйте конструктор напрямую, если создание простое. Фабрика полезна, когда нужны выбор реализации, проверка, кеширование или несколько согласованных зависимостей.

Singleton и ограниченные области жизни

Singleton объединяет единственный экземпляр с глобальным доступом. Глобальное изменяемое состояние, скрытые зависимости и влияние тестов друг на друга делают ручные singleton-объекты рискованными в Android.

Предпочитайте явные зависимости через конструктор, которыми управляет контейнер приложения или DI-фреймворк. Экземпляр в области Hilt уникален внутри своего компонента, но не существует вечно: смерть процесса уничтожает граф, а более узкие компоненты живут меньше. Потокобезопасное создание экземпляра также не делает его изменяемое состояние потокобезопасным.

Observer и реактивные потоки

Observer уведомляет зарегистрированных подписчиков об изменениях наблюдаемого объекта. Android listeners и callbacks часто следуют этой модели. StateFlow, SharedFlow и LiveData предлагают связанные наблюдаемые API, но отличаются повторной доставкой, буферизацией, жизненным циклом и обработкой ошибок.

Холодный Flow обычно отдельно запускает upstream-работу для каждого коллектора, поэтому это не просто список наблюдателей одного работающего источника. Собирайте UI-потоки через lifecycle-aware API и отменяйте callback-подписки при остановке их владельца.

Adapter

Adapter преобразует существующий интерфейс в другой, ожидаемый клиентом. Например, он может обернуть старый callback API в suspend-контракт или представить стороннее хранилище через интерфейс приложения.

Преобразование DTO в доменную модель обычно является mapper: оно меняет данные, а не интерфейс объекта. RecyclerView.Adapter согласует данные и создание View с протоколом RecyclerView, хотя его роль во фреймворке шире минимального паттерна GoF.

Strategy и State

Strategy скрывает взаимозаменяемые алгоритмы за одним контрактом. Валидаторы, правила расчёта цены или повторных запросов можно выбирать конфигурацией и использовать без большого условия.

State тоже делегирует поведение, но выбранный объект представляет текущее состояние владельца и меняется при переходах. Используйте sealed-иерархию и when, если набор состояний закрыт и прост; полиморфные объекты состояний полезны, когда каждое состояние содержит существенное поведение и переходы.

Decorator

Decorator оборачивает объект с тем же контрактом и добавляет поведение до или после делегирования. Логирование, метрики или авторизацию можно наслаивать без наследования:

class MeasuredRepository(
    private val delegate: UserRepository,
    private val metrics: Metrics,
) : UserRepository by delegate {
    override suspend fun user(id: UserId): User =
        metrics.measure("load_user") { delegate.user(id) }
}

Kotlin by устраняет шаблонную переадресацию. Если обёртка управляет доступом, намерение точнее описывает Proxy; если она преобразует интерфейс - Adapter.

Выбор паттерна

Сначала назовите конкретную проблему: создание объекта, несовместимый интерфейс, изменяемый алгоритм, зависящее от состояния поведение или дополнительная ответственность. Предпочитайте возможности языка и простую композицию, если они решают задачу ясно. Паттерн полезен, когда делает границы изменений понятнее, а не просто добавляет знакомые названия классов.

Связанные темы: ООП, SOLID и Основы DI.