Паттерны проектирования в Android-разработке

Курс посвящен изучению классических паттернов GoF и современных архитектурных подходов в контексте создания Android-приложений. Вы узнаете, как применять MVVM, Clean Architecture и другие паттерны для написания чистого и поддерживаемого кода.

1. Порождающие паттерны: Singleton, Builder и Factory Method в Android SDK

Порождающие паттерны: Singleton, Builder и Factory Method в Android SDK

Добро пожаловать на курс «Паттерны проектирования в Android-разработке». Это первая статья нашего цикла, и мы начнем с фундамента — порождающих паттернов (Creational Patterns).

В программировании, как и в архитектуре зданий, существуют типовые решения для часто встречающихся проблем. Эти решения называют паттернами проектирования. Они не являются готовым кодом, который можно просто скопировать (хотя иногда это так выглядит), а скорее представляют собой описание взаимодействия классов и объектов.

Сегодня мы разберем три самых популярных порождающих паттерна, которые вы неизбежно встретите в Android SDK и будете использовать в своих приложениях: Singleton (Одиночка), Builder (Строитель) и Factory Method (Фабричный метод).

Зачем нужны порождающие паттерны?

Порождающие паттерны абстрагируют процесс инстанцирования (создания) объектов. Они помогают сделать систему независимой от того, как именно создаются, компонуются и представляются её объекты.

Простыми словами: вместо того чтобы просто писать new ClassName(), мы используем хитрые механизмы, которые дают нам гибкость, контроль и чистоту кода.

---

1. Singleton (Одиночка)

Суть паттерна

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

Представьте, что в вашем офисе есть один главный принтер. Неважно, кто отправляет документ на печать — бухгалтер с третьего этажа или менеджер с первого — все запросы идут к одному и тому же физическому устройству. Если бы каждый создавал свой «виртуальный принтер», возник хаос. В программировании Singleton часто используется для объектов, которые управляют общими ресурсами: базой данных, сетевым клиентом или настройками приложения.

!Диаграмма, показывающая, как множество клиентов обращаются к единственному экземпляру Singleton.

Реализация в Kotlin

В Java реализация Singleton требовала написания приватного конструктора и статического метода getInstance(), часто с добавлением синхронизации для потокобезопасности (Double Check Locking). В Kotlin этот паттерн встроен в сам язык с помощью ключевого слова object.

Singleton в Android SDK

Android SDK активно использует концепцию Singleton, хотя часто скрывает её за вызовами методов. Классический пример — системные сервисы.

Когда вы вызываете context.getSystemService(Context.INPUT_METHOD_SERVICE), вы не создаете новый менеджер ввода каждый раз. Система возвращает вам ссылку на уже существующий сервис, который управляет клавиатурой во всей системе.

Другой пример — класс Application. Хотя технически это не чистый Singleton (система создает его за вас), в рамках процесса вашего приложения он существует в единственном экземпляре и часто используется для хранения глобального состояния.

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

Плюсы: * Гарантия единственного экземпляра. * Глобальный доступ (удобно, но опасно). * Ленивая инициализация (объект создается только при первом обращении).

Минусы: * Нарушение принципа единственной ответственности (SRP): класс управляет и своей логикой, и своим созданием. * Проблемы с тестированием: глобальное состояние очень трудно подменять (mock) в Unit-тестах. * Скрытые зависимости: если ваш класс внутри себя вызывает NetworkManager.makeRequest, это не видно в его конструкторе.

> Используйте Singleton осторожно. В современной Android-разработке часто предпочитают использовать Dependency Injection (например, Hilt или Koin), где объект помечается как @Singleton, но передается через конструктор.

---

2. Builder (Строитель)

Суть паттерна

Builder — это паттерн, который позволяет создавать сложные объекты пошагово. Он отделяет конструирование сложного объекта от его представления.

Представьте, что вы заказываете пиццу. Вы не просто говорите «дай мне пиццу». Вы говорите: «Тесто тонкое, соус томатный, добавь пепперони, убери лук, добавь сыр». Вы «строите» свой заказ шаг за шагом. Если бы для каждой комбинации ингредиентов существовал отдельный класс (например, PizzaThinDoughTomatoSaucePepperoniNoOnion), у нас были бы тысячи классов.

В коде Builder спасает нас от так называемого «телескопического конструктора», когда у класса 10 параметров, и половина из них — null.

!Визуализация пошагового процесса сборки сложного объекта.

Builder в Android SDK

Самый известный пример в Android — создание диалоговых окон AlertDialog и уведомлений Notification.

Взгляните на этот код:

Здесь AlertDialog.Builder — это вложенный класс, который накапливает настройки. Метод create() (или build()) собирает все параметры и выдает готовый объект AlertDialog.

Еще один пример — Retrofit. Для создания экземпляра мы используем билдер:

Реализация своего Builder в Kotlin

В Kotlin потребность в классическом паттерне Builder снижается благодаря именованным аргументам и значениям по умолчанию.

Вместо громоздкого Билдера:

В Kotlin мы пишем:

Однако, если инициализация объекта требует сложной логики (валидация комбинации полей, последовательные вычисления), паттерн Builder всё еще актуален даже в Kotlin.

---

3. Factory Method (Фабричный метод)

Суть паттерна

Factory Method — это паттерн, который определяет интерфейс для создания объекта, но позволяет подклассам решать, какой класс инстанцировать. Фабричный метод позволяет классу делегировать создание объектов своим подклассам.

Аналогия из жизни: логистическая компания. У неё есть метод «Доставить груз». Если это автоперевозки, создается объект «Грузовик». Если морские перевозки — создается объект «Корабль». Клиентский код (менеджер) просто говорит «Доставить», не вдаваясь в подробности, как именно будет создан транспорт.

!Диаграмма, демонстрирующая принцип работы Фабричного метода: делегирование создания объекта конкретным реализациям.

Factory Method в Android SDK

Отличный пример — ViewModelProvider.Factory.

В Android ViewModel не может быть создана просто через конструктор, если у неё есть параметры, потому что её жизненным циклом управляет система. Нам нужно подсказать системе, как создавать нашу ViewModel.

Здесь метод create — это и есть тот самый фабричный метод. Фреймворк Android вызовет его, когда ему потребуется экземпляр MyViewModel, и фабрика вернет готовый объект с внедренным репозиторием.

Еще один пример, с которым вы сталкиваетесь постоянно — LayoutInflater.

Метод inflate работает как фабрика: он читает XML-файл и на его основе создает иерархию объектов View (TextView, Button, LinearLayout и т.д.). Вы не пишете new TextView(...) вручную для каждого элемента, это делает фабрика.

Статический фабричный метод

Частный случай, популярный в Java и Android — статические методы создания. Например, в Fragment:

Метод newInstance скрывает сложность настройки фрагмента (создание Bundle, установка аргументов) и возвращает готовый объект. Это упрощает использование класса для внешнего кода.

---

Итоги

Мы рассмотрели три ключевых порождающих паттерна:

  • Singleton — когда нужен один экземпляр на всё приложение (но помните о проблемах с тестированием!).
  • Builder — когда нужно создать сложный объект с множеством параметров конфигурации.
  • Factory Method — когда нужно делегировать логику создания объекта или скрыть её детали.
  • Понимание этих паттернов поможет вам не только писать более чистый код, но и лучше понимать, как работает сам Android SDK под капотом. В следующей статье мы перейдем к структурным паттернам и узнаем, как эффективно компоновать объекты и классы.

    2. Структурные паттерны: Adapter для RecyclerView, Decorator и Facade

    Структурные паттерны: Adapter для RecyclerView, Decorator и Facade

    Приветствую вас во второй части курса «Паттерны проектирования в Android-разработке». В прошлой статье мы разобрали порождающие паттерны (Singleton, Builder, Factory Method), которые отвечают за то, как создаются объекты. Теперь, когда у нас есть кирпичики, пришло время узнать, как правильно складывать из них стены.

    Сегодняшняя тема — структурные паттерны (Structural Patterns). Они определяют, как классы и объекты компонуются в более крупные структуры. Эти паттерны помогают гарантировать, что при изменении одной части системы не рухнет вся остальная архитектура.

    Мы сосредоточимся на трех паттернах, без которых невозможно представить современную Android-разработку: Adapter (Адаптер), Decorator (Декоратор) и Facade (Фасад).

    ---

    1. Adapter (Адаптер)

    Проблема совместимости

    Представьте, что вы приехали в другую страну, а вилка вашего зарядного устройства не подходит к местной розетке. У вас есть источник энергии (розетка) и потребитель (зарядка), но они не могут взаимодействовать напрямую из-за разных интерфейсов. Что вы делаете? Покупаете переходник.

    В программировании Adapter выполняет ту же роль: он позволяет объектам с несовместимыми интерфейсами работать вместе.

    !Адаптер преобразует вызовы клиента в формат, понятный сервису.

    Adapter в Android: RecyclerView

    Самый известный пример использования этого паттерна в Android — это RecyclerView.Adapter.

    Давайте разберем архитектуру RecyclerView. У нас есть:

  • Данные (например, List<User>). Это наш источник, который ничего не знает о том, как рисовать интерфейс.
  • RecyclerView. Это сложный компонент UI, который умеет эффективно отображать списки, переиспользовать View, скроллить их. Но он понятия не имеет, что такое класс User и какие поля нужно вывести на экран.
  • Здесь на сцену выходит Адаптер. Он служит мостом между данными и списком.

    В этом примере UserAdapter адаптирует список объектов User к интерфейсу, который ожидает RecyclerView. Метод onBindViewHolder буквально переводит данные с языка моделей на язык View.

    Когда применять Adapter?

    * Когда вы хотите использовать существующий класс, но его интерфейс не соответствует остальному коду. * Когда нужно создать класс для повторного использования, который должен взаимодействовать с объектами, неизвестными заранее (как RecyclerView взаимодействует с любыми данными).

    ---

    2. Decorator (Декоратор)

    Принцип «Матрешки»

    Decorator — это структурный паттерн, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обертки».

    Представьте, что вы выходите на улицу зимой. Вы надеваете футболку. Холодно? Надеваете свитер поверх футболки. Все еще холодно? Надеваете куртку. Вы (объект) остались тем же человеком, но теперь у вас есть новые свойства (теплоизоляция, защита от ветра), полученные за счет слоев одежды.

    В отличие от наследования, где функциональность добавляется статически (во время компиляции), декораторы позволяют комбинировать поведение динамически.

    !Декораторы оборачивают объект, добавляя поведение до или после вызова основного метода.

    Decorator в Android SDK: ContextWrapper

    В Android паттерн Декоратор встречается повсеместно в работе с вводом-выводом (Java IO streams), но самый «родной» пример — это класс Context.

    Класс Context — это абстрактный класс. Однако, если вы посмотрите на иерархию Activity, Service или Application, вы увидите, что они часто наследуются от ContextWrapper.

    ContextWrapper — это классический декоратор. Он принимает в конструктор другой Context (базовый) и делегирует ему все вызовы.

    Зачем это нужно? Это позволяет подменять поведение контекста. Например, ContextThemeWrapper (родитель Activity) оборачивает базовый контекст и добавляет к нему логику работы с темами (стилями), переопределяя методы получения ресурсов.

    Реализация в Kotlin: Делегирование

    Kotlin имеет встроенную поддержку паттерна Декоратор через ключевое слово by. Это называется делегированием классов.

    Допустим, у нас есть сервис для сохранения данных, и мы хотим добавить к нему логирование, не меняя код самого сервиса.

    Здесь by inner говорит компилятору: «Реализуй все методы интерфейса DataSaver, перенаправляя вызовы в объект inner, если я не переопределил их явно».

    ---

    3. Facade (Фасад)

    Простой пульт от сложной системы

    Facade — это паттерн, который предоставляет простой интерфейс к сложной системе классов, библиотеке или фреймворку.

    Вспомните автомобиль. Под капотом находятся сотни сложных механизмов: инжектор, свечи зажигания, поршни, трансмиссия. Но водителю не нужно управлять каждым из них вручную. У него есть Фасад — руль и педали. Поворот ключа зажигания запускает сложнейшую последовательность действий, скрытую от пользователя.

    !Фасад скрывает сложность системы за простым интерфейсом.

    Facade в Android: Retrofit и Glide

    В Android мы постоянно используем библиотеки, которые являются фасадами.

    #### Пример 1: Glide / Picasso Загрузка картинки из сети — это сложный процесс: нужно создать сетевой запрос, скачать байты, декодировать их в Bitmap, изменить размер, закэшировать в памяти и на диске, и только потом вставить в ImageView.

    Благодаря фасаду Glide, мы пишем:

    Весь «зоопарк» классов (Downloader, Cache, Decoder, Transformation) скрыт за этим простым вызовом.

    #### Пример 2: Собственный Фасад

    Часто в проектах полезно создавать свои фасады для группировки логики. Например, у вас есть система авторизации, которая требует работы с API, базой данных и SharedPreferences.

    Вместо того чтобы вызывать эти сервисы из Activity по отдельности, создайте AuthRepository (или AuthManager), который выступит фасадом.

    Теперь UI-слой вызывает только authFacade.performLogin(token), не зная о существовании базы данных или преференсов.

    Отличие Facade от Adapter

    Это частый вопрос на собеседованиях. * Adapter меняет интерфейс существующего объекта, чтобы он подходил под ожидания клиента (делает квадратное круглым). Facade создает новый*, упрощенный интерфейс для взаимодействия с группой объектов (делает сложное простым).

    ---

    Итоги

    Сегодня мы изучили три структурных паттерна, которые помогают организовывать код:

  • Adapter — спасает, когда нужно «подружить» несовместимые интерфейсы (как RecyclerView.Adapter).
  • Decorator — позволяет оборачивать объекты, добавляя им новые возможности без наследования (как ContextWrapper или Kotlin Delegation).
  • Facade — прячет сложную систему за простой «дверью» (как Glide или Retrofit).
  • Эти паттерны делают код более гибким и читаемым. В следующей статье мы перейдем к поведенческим паттернам (Behavioral Patterns) и узнаем, как эффективно организовать общение между объектами, используя Observer, Strategy и Command.

    3. Поведенческие паттерны: Observer с LiveData, Command и Strategy

    Поведенческие паттерны: Observer с LiveData, Command и Strategy

    Добро пожаловать в третью часть нашего курса «Паттерны проектирования в Android-разработке». Мы уже прошли долгий путь: научились правильно создавать объекты с помощью порождающих паттернов и выстраивать их в надежные конструкции благодаря структурным паттернам.

    Но архитектура приложения — это не только статика. Это живой организм, где компоненты постоянно общаются друг с другом, реагируют на действия пользователя и изменения данных. Здесь на сцену выходят поведенческие паттерны (Behavioral Patterns).

    Поведенческие паттерны заботятся об эффективной коммуникации между объектами и распределении ответственности. Сегодня мы разберем три фундаментальных паттерна, которые лежат в основе реактивного программирования и гибкой логики Android-приложений: Observer (Наблюдатель), Strategy (Стратегия) и Command (Команда).

    ---

    1. Observer (Наблюдатель)

    Суть паттерна

    Observer — это паттерн, который создает механизм подписки, позволяющий одним объектам следить за событиями, происходящими в других объектах.

    Представьте, что вы ждете выхода нового сезона любимого сериала. Вы можете каждый день звонить в студию и спрашивать: «Уже вышло?». Это неэффективно и раздражает студию. Вместо этого вы оформляете подписку на уведомления. Как только сезон выйдет, студия сама отправит вам сообщение. Вы (Наблюдатель) реагируете на изменение состояния Студии (Издателя).

    !Диаграмма показывает, как один объект оповещает множество подписчиков об изменении своего состояния.

    Observer в Android: LiveData

    В мире Android паттерн Observer является королем UI. Раньше мы использовали интерфейсы-слушатели (OnClickListener, OnScrollListener), которые являются классической реализацией этого паттерна. Однако с появлением Android Architecture Components стандартом стал LiveData (и его современный преемник StateFlow).

    LiveData — это реализация паттерна Observer, которая знает о жизненном цикле (Lifecycle-aware). Это решает главную проблему классического Observer в Android: утечки памяти и краши при попытке обновить UI, которого уже нет на экране.

    Рассмотрим пример. У нас есть ViewModel, которая загружает данные пользователя, и Activity, которая хочет их отобразить.

    В этом примере Activity не опрашивает ViewModel постоянно. Она просто «подписывается» на изменения. Как только _userName.value обновится, сработает лямбда-выражение внутри observe.

    Плюсы и минусы

    Плюсы: * Ослабление связности между компонентами (ViewModel не знает ничего об Activity). * Реактивность UI: интерфейс всегда отражает актуальное состояние данных.

    Минусы: * Сложность отладки: поток управления неочевиден, так как вызовы происходят неявно. * Риск утечек памяти (если не использовать Lifecycle-aware компоненты).

    ---

    2. Strategy (Стратегия)

    Суть паттерна

    Strategy — это паттерн, который определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритм независимо от клиентов, которые его используют.

    Представьте навигатор. Вы хотите добраться из точки А в точку Б. Навигатор предлагает вам варианты: «На машине», «Пешком», «На общественном транспорте». Цель одна (добраться), но алгоритмы (стратегии) разные. Вы можете сменить стратегию на лету, если, например, началась пробка.

    В программировании это выглядит так: у вас есть класс, который делает какую-то работу, но конкретный способ выполнения этой работы выносится в отдельный интерфейс.

    !Схема показывает, как контекст делегирует выполнение задачи сменному объекту-стратегии.

    Математический пример: Расчет скидок

    Допустим, мы разрабатываем приложение для магазина. Цена товара зависит от типа клиента (обычный, VIP, сотрудник). Мы можем написать огромный if-else, а можем использовать Стратегию.

    Формула расчета цены может быть записана так:

    где — итоговая цена, — базовая цена товара, а — коэффициент скидки, зависящий от стратегии.

    Реализация в Kotlin

    В Android SDK паттерн Command часто встречается в виде интерфейса Runnable. Когда вы пишете Handler().post(runnable), вы создаете команду и передаете её в очередь сообщений главного потока.

    Когда использовать Command?

  • Нужна операция отмены (Undo/Redo).
  • Нужно отложенное выполнение операций (очереди задач).
  • Нужно параметризовать объекты выполняемым действием.
  • ---

    Итоги

    Сегодня мы рассмотрели три мощных инструмента для управления поведением приложения:

  • Observer (LiveData) — позволяет объектам «слушать» изменения данных, не создавая жестких связей. Идеально для связки ViewModel и UI.
  • Strategy — позволяет подменять алгоритмы на лету. Идеально для сортировок, валидации или смены LayoutManager.
  • Command — инкапсулирует действие в объект. Идеально для реализации истории действий и отложенных задач.
  • Эти паттерны делают ваш код гибким и готовым к изменениям. В следующей, заключительной статье курса, мы поговорим о архитектурных паттернах (MVC, MVP, MVVM) и увидим, как все изученные нами паттерны собираются в единую картину построения приложения.

    4. Архитектурные паттерны представления: эволюция от MVC к MVVM и MVI

    Архитектурные паттерны представления: эволюция от MVC к MVVM и MVI

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

    Сегодня мы поднимемся на уровень выше. Мы поговорим не просто о взаимодействии отдельных классов, а об архитектуре всего экрана или приложения. Речь пойдет об архитектурных паттернах представления (Presentation Patterns).

    В Android-разработке долгое время царил хаос, известный как «God Activity» (Божественная Activity) — класс, который знал всё, умел всё и занимал 2000 строк кода. Чтобы победить этого монстра, разработчики прошли эволюционный путь от MVC к MVP, затем к MVVM, и сейчас мы наблюдаем расцвет MVI.

    Давайте разберем эту эволюцию, чтобы понять, какую проблему решает каждый из подходов.

    Проблема «Божественного объекта»

    Прежде чем говорить о решениях, определим проблему. Если вы пишете весь код в Activity или Fragment:

  • Сложность поддержки: Логика перемешана с версткой и системными вызовами.
  • Невозможность тестирования: Вы не можете написать Unit-тест для логики, которая намертво прибита к Android Context и жизненному циклу.
  • Хрупкость: Изменение в UI может сломать бизнес-логику.
  • Архитектурные паттерны призваны разделить код на три основных слоя: * Model (Модель): Данные и бизнес-логика (БД, API, интеракторы). * View (Представление): Отображение данных (Activity, Fragment, XML, Compose). * Controller / Presenter / ViewModel: Посредник, который связывает данные и отображение.

    ---

    1. MVC (Model-View-Controller) — С чего всё начиналось

    MVC — это старейший паттерн, пришедший из веба и десктопной разработки 70-х годов. В классическом понимании: * Controller реагирует на действия пользователя и обновляет Модель. * Model при изменении оповещает View. * View отображает данные Модели.

    !Классическая схема взаимодействия компонентов в MVC, образующая треугольную зависимость.

    Почему MVC не прижился в Android?

    В Android очень сложно отделить View от Controller. Activity (или Fragment) по своей природе является и тем, и другим. Она загружает разметку (View) и обрабатывает жизненный цикл и ввод пользователя (Controller).

    Попытки реализовать MVC в Android обычно приводили к тому, что Модель была отделена, а View и Controller сливались в один гигантский класс. Это не решало проблему тестируемости.

    ---

    2. MVP (Model-View-Presenter) — Первая революция

    Чтобы разорвать порочный круг зависимости от Activity, появился паттерн MVP.

    Главная идея: View должна быть глупой (Passive View). Она не должна принимать никаких решений, только отображать то, что скажет Presenter, и сообщать ему о кликах.

    Как это работает?

  • View (Activity) реализует интерфейс (например, LoginView), в котором есть методы showError(), showSuccess(), showLoading().
  • Presenter — это обычный Java/Kotlin класс, который не содержит ни одной строки Android-кода (нет Context, нет View). Он общается с View только через интерфейс.
  • Model — источник данных.
  • !В MVP Presenter полностью изолирует View от Model, выступая посредником.

    Плюсы и минусы MVP

    Плюсы: * Тестируемость: Presenter можно легко покрыть Unit-тестами, так как он не зависит от Android SDK. * Разделение ответственности: View занимается только рисованием.

    Минусы: * Много кода: Нужно писать интерфейсы для каждой Activity. * Жизненный цикл: Presenter нужно вручную привязывать и отвязывать (attach/detach), чтобы не вызвать краш при попытке обновить закрытую Activity.

    ---

    3. MVVM (Model-View-ViewModel) — Стандарт индустрии

    С появлением Android Architecture Components (Jetpack) компания Google официально рекомендовала MVVM. Этот паттерн устраняет главную проблему MVP — жесткую привязку Presenter к интерфейсу View.

    В MVVM ViewModel ничего не знает о View. У неё нет ссылки на интерфейс View. Как же они общаются? С помощью паттерна Observer (Наблюдатель), который мы изучили в прошлой лекции.

    Принцип работы

  • ViewModel хранит состояние данных в наблюдаемых контейнерах (LiveData, StateFlow).
  • View (Activity/Fragment) подписывается на эти данные.
  • Когда данные меняются, View автоматически обновляется.
  • !MVVM использует реактивный подход: ViewModel выставляет потоки данных, а View на них подписывается.

    Почему MVVM победила MVP?

    * Переживание поворота экрана: Архитектурная ViewModel в Android сохраняется при пересоздании Activity. В MVP Presenter обычно умирал и создавался заново (или требовал сложных костылей). * Отсутствие утечек памяти: Так как ViewModel не держит ссылку на View, риск утечки контекста минимален. * Меньше кода: Не нужны интерфейсы-контракты.

    ---

    4. MVI (Model-View-Intent) — Реактивное будущее

    С развитием декларативных UI (Jetpack Compose) и реактивного программирования (RxJava, Coroutines Flow) популярность набрал паттерн MVI.

    MVI основан на концепции однонаправленного потока данных (Unidirectional Data Flow — UDF). В отличие от MVVM, где у вас может быть 10 разных LiveData для разных полей, в MVI состояние экрана описывается одним неизменяемым объектом.

    Математически UI в MVI можно описать формулой:

    где (View) — это то, что видит пользователь, — функция отображения (рендеринга), а (State) — единое состояние системы в данный момент времени.

    Основные компоненты MVI

  • Model (State): Единый объект состояния (обычно data class), описывающий весь экран. Например: Loading, Content(data), Error(msg).
  • View: Отображает состояние и отправляет намерения (Intents).
  • Intent: Намерение пользователя сделать что-то (нажать кнопку, обновить свайпом). Не путать с android.content.Intent!
  • Цикл MVI

  • Пользователь нажимает кнопку -> генерируется Intent.
  • Intent обрабатывается и меняет Model (State).
  • Новый State прилетает во View.
  • View перерисовывается целиком (или частично).
  • !Однонаправленный поток данных в MVI гарантирует предсказуемость изменений состояния.

    Плюсы и минусы MVI

    Плюсы: * Единый источник правды: Состояние экрана всегда согласовано. Не может быть ситуации, когда «загрузка идет», но «ошибка показана» одновременно, если это не предусмотрено стейтом. * Отладка: Если вы залогируете все Intent и State, вы сможете воспроизвести любой баг (Time Travel Debugging).

    Минусы: * Сложность: Высокий порог входа. * Много объектов: При каждом изменении создается новый объект State (нагрузка на сборщик мусора, хотя в современных JVM это не критично).

    ---

    Сводная таблица эволюции

    | Паттерн | Кто управляет View? | Связь View и Logic | Состояние | Тестируемость | | :--- | :--- | :--- | :--- | :--- | | MVC | Controller + Activity | Тесная (Spaghetti) | Размыто | Низкая | | MVP | Presenter | Через интерфейс | В Presenter | Высокая | | MVVM | View (через подписку) | Observer (LiveData) | В ViewModel | Высокая | | MVI | View (реактивно) | Поток состояний (StateFlow) | Единый Immutable объект | Максимальная |

    ---

    Заключение

    Выбор архитектуры зависит от задачи: * Для простых экранов или старых проектов MVP всё еще жив. * Для большинства современных приложений стандартом является MVVM. * Для сложных экранов с запутанной логикой и Jetpack Compose идеально подходит MVI.

    Главное, что вы должны вынести из этого курса: паттерны — это не самоцель, а инструмент. Singleton помогает управлять ресурсами, Adapter связывает несовместимое, Observer оживляет интерфейс, а MVVM/MVI наводят порядок в структуре приложения.

    Используйте их мудро, пишите чистый код, и пусть ваши приложения никогда не падают с NullPointerException!

    Спасибо, что были с нами на этом курсе.

    5. Принципы Clean Architecture и паттерн Dependency Injection

    Принципы Clean Architecture и паттерн Dependency Injection

    Добро пожаловать на финальную лекцию нашего курса «Паттерны проектирования в Android-разработке». Мы прошли долгий путь: от создания одиночных объектов с помощью Singleton до управления сложными состояниями экранов через MVI.

    Однако, когда приложение разрастается от одного экрана до десятков модулей, простого использования MVVM становится недостаточно. Код начинает запутываться, классы знают слишком много друг о друге, а изменение одной строчки в базе данных ломает UI. Здесь нам на помощь приходят глобальные архитектурные принципы.

    Сегодня мы разберем «Священный Грааль» современной разработки — Clean Architecture (Чистая Архитектура) и механизм, который заставляет её работать — Dependency Injection (Внедрение Зависимостей).

    Что такое Clean Architecture?

    Термин Clean Architecture был популяризирован Робертом Мартином (известным как Дядюшка Боб) в 2012 году. Это не набор жестких правил, а философия разделения ответственности.

    Главная цель Чистой Архитектуры — создать систему, которая:

  • Не зависит от фреймворков. Android SDK — это лишь инструмент, а не суть вашего приложения.
  • Тестируема. Бизнес-логику можно проверить без эмулятора и UI.
  • Не зависит от UI. Интерфейс можно легко переписать (например, с XML на Compose), не трогая логику.
  • Не зависит от Базы Данных. Вы можете сменить Room на Realm или SQLDelight, и бизнес-логика этого не заметит.
  • Слои Чистой Архитектуры

    В Android-разработке принято адаптировать классическую схему Дядюшки Боба в три основных слоя:

    !Диаграмма слоев Clean Architecture и направление зависимостей.

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

    1. Domain Layer (Слой предметной области)

    Это сердце вашего приложения. Самый важный и самый независимый слой.

    * Что содержит: Сущности (Entities), Интерфейсы репозиториев и Сценарии использования (Use Cases). * Зависимости: Никаких. Он не знает ни о базе данных, ни об Android SDK, ни о Retrofit. * Язык: Чистый Kotlin.

    Здесь живут правила вашего бизнеса. Например, правило «Пользователь не может оформить заказ, если корзина пуста» должно быть написано именно здесь.

    2. Data Layer (Слой данных)

    Этот слой отвечает за то, откуда берутся данные. Он является реализацией контрактов, определенных в Domain слое.

    * Что содержит: Реализации репозиториев, Источники данных (Data Sources), DTO (Data Transfer Objects), Базы данных (Room), Сетевые клиенты (Retrofit). * Зависимости: Зависит от Domain слоя.

    Здесь происходит магия превращения JSON-ответа от сервера в удобные бизнес-объекты.

    3. Presentation Layer (Слой представления)

    Это то, с чем взаимодействует пользователь. Мы подробно разбирали его в прошлой лекции (MVVM, MVI).

    * Что содержит: UI (Activity, Fragment, Composable), ViewModels, Mappers (из Domain моделей в UI модели). * Зависимости: Зависит от Domain слоя.

    Dependency Rule (Правило Зависимостей)

    Самое главное правило Чистой Архитектуры можно описать математически через теорию множеств зависимостей.

    Пусть — внутренний слой (Domain), а — внешний слой (Data или Presentation). Правило гласит:

    где — множество зависимостей внутреннего слоя, — элементы внешнего слоя, а — пустое множество. Это означает, что внутренний слой никогда не должен ссылаться на классы из внешнего слоя.

    Простыми словами: Стрелки зависимостей всегда указывают внутрь круга.

    * Activity (Presentation) знает о ViewModel. * ViewModel знает о UseCase (Domain). * RepositoryImpl (Data) знает о Repository интерфейсе (Domain). * Domain не знает ни о ком.

    Dependency Injection (Внедрение Зависимостей)

    Если слои должны быть независимы, как же они взаимодействуют? Как ViewModel получает доступ к UseCase, если мы не можем просто написать val useCase = new UseCase() (ведь это создаст жесткую связь)?

    Здесь на сцену выходит паттерн Dependency Injection (DI).

    Суть паттерна

    Dependency Injection — это стиль настройки объекта, при котором поля объекта задаются внешней сущностью. Мы не создаем зависимости внутри класса, мы их получаем.

    Без DI (Плохо):

    С DI (Хорошо):

    DI Frameworks в Android

    Ручное внедрение зависимостей (Manual DI) возможно в маленьких проектах, но в больших это превращается в ад из фабрик. Поэтому в Android используются специальные фреймворки:

  • Hilt (на базе Dagger 2): Официальный стандарт от Google. Работает на кодогенерации (compile-time). Обеспечивает строгую проверку графа зависимостей при компиляции.
  • Koin: Популярный Service Locator (хотя позиционируется как DI). Работает в runtime. Проще в настройке, но ошибки могут возникнуть во время работы приложения.
  • Собираем всё вместе: Пример

    Давайте посмотрим, как Clean Architecture и DI работают вместе на примере функции «Получить список пользователей».

    1. Domain Layer (Чистый Kotlin)

    Сначала определяем интерфейс и сценарий использования.

    Обратите внимание на аннотацию @Inject. Это маркер для Hilt, говорящий: «Если кому-то нужен этот класс, вот как его создать».

    2. Data Layer (Android + Libraries)

    Реализуем интерфейс, работая с сетью.

    Чтобы Hilt знал, что UserRepositoryImpl — это реализация UserRepository, нам нужен модуль:

    3. Presentation Layer (Android UI)

    Наконец, используем это во ViewModel.

    Преимущества такого подхода

  • Легкость изменений. Если завтра бэкенд изменит формат JSON, вы поменяете код только в Data слое. Domain и Presentation даже не узнают об этом.
  • Параллельная разработка. Один разработчик может верстать UI (Presentation), другой писать логику (Domain), а третий настраивать базу данных (Data), договорившись только об интерфейсах.
  • Тестирование. Вы можете написать Unit-тест для GetUsersUseCase, передав ему FakeUserRepository, который возвращает тестовые данные. Вам не нужен реальный сервер или эмулятор.
  • Заключение курса

    Мы завершаем наш курс по паттернам проектирования. Мы начали с кирпичиков (Singleton, Builder), научились строить стены (Adapter, Facade), провели проводку (Observer, Strategy) и, наконец, построили надежный небоскреб с помощью Clean Architecture.

    Помните: паттерны — это инструменты для решения проблем, а не самоцель. Не пытайтесь впихнуть Clean Architecture в приложение-визитку из одного экрана. Но когда вы строите сложную систему, эти принципы станут вашим спасением от хаоса.

    Пишите чистый код, тестируйте его и наслаждайтесь архитектурой!