Распространенные паттерны проектирования и архитектуры приложений на Android

10 июня 2021

Узнайте, как сделать код под Android более чистым и понятным с помощью распространенных паттернов проектирования для приложений на Android. Будущий Вы - то, что вам нужно!

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

Представим, что вы планируете оставить свою текущую работу и хотите облегчить жизнь тем, кто будет делать вашу работу в дальнейшем. Вы же не хотите, чтобы они "нахваливали" ваш спагетти-код, который вы оставили.

В этой статье представлены наиболее распространенные шаблоны проектирования и архитектуры приложений, которые вы можете использовать при разработке приложений. Шаблоны проектирования - это многократно используемые решения распространенных проблем программного обеспечения. Архитектуры приложений предоставляют решения для проблем с потоком данных или расширяемостью приложения.

В этом туториале вы не найдете исчерпывающий список шаблонов проектирования и архитектур приложений. Считайте это «практическим справочником» и отправной точкой для дальнейшего обучения.

Заметка

Предполагается, что вы знакомы с основами разработки под Android.

 

Приступим

Будущий Вы: «Будет ли в проекте место, где мне придется повторно использовать один и тот же фрагмент кода?».

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

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

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

В следующих разделах вы рассмотрите эти шаблоны из каждой категории и увидите, как они применимы к Android:

Порождающие шаблоны:

  • Builder
  • Dependency Injection
  • Singleton
  • Factory

Структурные шаблоны:

  • Adapter
  • Facade
  • Decorator
  • Composite

Поведенческие шаблоны:

  • Command
  • Observer
  • Strategy
  • State

Порождающие шаблоны

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

Builder

Представьте, что в каком-то ресторане вы создаете свой сэндвич: вы выбираете хлеб, ингредиенты и приправы, которые хотите добавить в сэндвич, из контрольного списка на листке бумаги. Несмотря на то, что в списке вам предлагается приготовить свой собственный сэндвич, вы только заполняете форму и сдаете ее на прилавок. Вы не собираете сэндвич, а просто указываете из чего его сделать и потребляете. :]
Точно так же шаблон Builder (конструктор) упрощает создание объектов, таких как нарезка хлеба и укладка солений, из своего представления - вкусного сэндвича. Таким образом, один и тот же процесс построения может создавать объекты одного класса с разными свойствами.

В Android примером шаблона Builder является AlertDialog.Builder:

AlertDialog.Builder(this)
  .setTitle("Sandwich Dialog")
  .setMessage("Please use the spicy mustard.")
  .setNegativeButton("No thanks") { dialogInterface, i ->
    // "No thanks" action
  }
  .setPositiveButton("OK") { dialogInterface, i ->
    // "OK" action
  }
  .show()

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

Другой набор вариантов привел бы к совершенно другому сэндвичу, то есть предупреждению. :]

Dependency Injection

Dependency Injection (инъекция зависимостей) похоже на переезд в меблированную квартиру. Все необходимое уже есть. Вам не нужно ждать доставки мебели или следовать инструкциям IKEA, чтобы собрать книжную полку Borgsjö.

С точки зрения программного обеспечения, инъекция зависимостей позволяет вам предоставить все необходимые объекты для создания экземпляра нового объекта. Этому новому объекту не нужно создавать или настраивать сами объекты.

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

В настоящее время существует три основных библиотеки для внедрения зависимостей: Dagger «2», Dagger Hilt и Koin. Давайте посмотрим на пример с Dagger. В нем вы аннотируете класс с помощью @Module и заполняете его такими методами @Provides:

@Module
class AppModule(private val app: Application) {
  @Provides
  @Singleton
  fun provideApplication(): Application = app

  @Provides
  @Singleton
  fun provideSharedPreferences(app: Application): SharedPreferences {
    return app.getSharedPreferences("prefs", Context.MODE_PRIVATE)
  }
}

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

Затем вы создаете интерфейс Component, в котором будут перечислены ваши модули и классы, которые вы будете вводить:

@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
  fun inject(activity: MainActivity)
  // ...
}

Этот компонент связывает Модуль откуда эти зависимости идут с местом инъекций этих зависимостей.

Наконец, вы используете аннотацию @Inject для запроса зависимости там, где она вам нужна, вместе с lateinit для инициализации свойства, не допускающего значения NULL, после создания содержащего объекта.

@Inject lateinit var sharedPreferences: SharedPreferences

В качестве примера вы можете использовать этот вариант в своей MainActivity, а затем использовать локальное хранилище, при этом Activity не нужно знать, как появился объект SharedPreferences.

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

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

Singleton

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

В Kotlin ключевое слово object  объявляет синглтон без необходимости указывать статический экземпляр, как в других языках:

object ExampleSingleton {
  fun exampleMethod() {
    // ...
  }
}

Когда вам нужно получить доступ к членам объекта синглтона, вы делаете такой вызов:

ExampleSingleton.exampleMethod()

Под капотом статическое поле INSTANCE поддерживает объект Kotlin. Итак, если вам нужно использовать объект Kotlin из кода Java, вы измените вызов следующим образом:

ExampleSingleton.INSTANCE.exampleMethod();

Используя объект, вы будете знать, что используете один и тот же экземпляр этого класса во всем приложении.

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

Factory

Как следует из названия, Factory заботится обо всей логике создания объекта. В этом шаблоне класс factory контролирует, какой объект создать. Шаблон factory пригодится при работе со многими обычными объектами. Вы можете использовать его там, где не хотите указывать конкретный класс.

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

// 1
interface HostingPackageInterface {
  fun getServices(): List
}

// 2
enum class HostingPackageType {
  STANDARD,
  PREMIUM
}

// 3
class StandardHostingPackage : HostingPackageInterface {
  override fun getServices(): List {
    return ...
  }
}

// 4
class PremiumHostingPackage : HostingPackageInterface {
  override fun getServices(): List {
    return ...
  }
}

// 5
object HostingPackageFactory {
  // 6
  fun getHostingFrom(type: HostingPackageType): HostingPackageInterface {
    return when (type) {
      HostingPackageType.STANDARD -> {
        StandardHostingPackage()
      }
      HostingPackageType.PREMIUM -> {
        PremiumHostingPackage()
      }
    }
  }
}

Давайте рассмотрим пошагово:

  1. Это базовый интерфейс для всех планов хостинга.
  2. Перечисление (энум) определяет все типы пакетов хостинга.
  3. StandardHostingPackage соответствует интерфейсу и реализует требуемый метод для вывода списка всех служб.
  4. PremiumHostingPackage соответствует интерфейсу и реализует требуемый метод для вывода списка всех сервисов.
  5. HostingPackageFactory - это класс синглтона со вспомогательным методом.
  6. getHostingFrom внутри HostingPackageFactory отвечает за создание всех объектов.

Вы можете использовать это так:

val standardPackage = HostingPackageFactory.getHostingFrom(HostingPackageType.STANDARD)

Это поможет сохранить создание объекта в одном классе. При неправильном использовании класс Factory может раздуться из-за чрезмерного количества объектов. Тестирование затруднится, поскольку класс factory сам отвечает за все объекты.

Структурные шаблоны

Будущий Вы: «Итак, когда я открою этот класс, как я запомню, что он делает и как это собрано?».

В будущем вы несомненно оцените Структурные шаблоны, которые использовали для организации внутренностей ваших классов и объектов в знакомые структуры, которые выполняют типичные задачи. Adapter и Facade - два часто встречающихся шаблона в Android.

Adapter

В знаменитой сцене фильма «Аполлон-13» команда инженеров вставила квадратный стержень в круглое отверстие. Образно говоря, это роль адаптера. С точки зрения программного обеспечения, этот шаблон позволяет двум несовместимым классам работать вместе, преобразовывая интерфейс класса в интерфейс, ожидаемый клиентом.

Учитывайте бизнес-логику своего приложения. Это может быть Product, User или Tribble. То есть образно это квадратный стержень. Между тем, RecyclerView - это один и тот же базовый объект во всех приложениях Android. Это круглое отверстие.

В этой ситуации вы можете использовать подкласс RecyclerView. Adapter и реализовать необходимые методы, чтобы все работало:

class TribbleAdapter(private val tribbles: List) : RecyclerView.Adapter() {
  override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): TribbleViewHolder {
    val inflater = LayoutInflater.from(viewGroup.context)
    val view = inflater.inflate(R.layout.row_tribble, viewGroup, false)
    return TribbleViewHolder(view)
  }

  override fun onBindViewHolder(viewHolder: TribbleViewHolder, itemIndex: Int) {
    viewHolder.bind(tribbles[itemIndex])
  }

  override fun getItemCount() = tribbles.size
}

RecyclerView не знает, что такое Tribble, поскольку он никогда не видел ни одной серии Star Trek, даже новых фильмов :]. Вместо этого задача адаптера - обработать данные и отправить команду bind в правильный ViewHolder.

Facade

Шаблон Facade предоставляет интерфейс более высокого уровня, который упрощает использование набора других интерфейсов. Следующая диаграмма иллюстрирует эту идею более подробно:

 

Если вашей Activity нужен список книг, она должна иметь возможность запрашивать один объект для этого списка, не понимая внутренней работы вашего локального хранилища, кэша и клиента API. Помимо сохранения чистоты и краткости кода Activity и Fragment, это позволяет вам вносить любые необходимые изменения в реализацию API, не влияя на Activity.

Retrofit от Square - это библиотека Android с открытым исходным кодом, которая помогает реализовать шаблон Facade. Вы создаете интерфейс для предоставления данных API клиентским классам следующим образом:

interface BooksApi {
  @GET("books")
  fun listBooks(): Call
}

Клиенту необходимо вызвать listBooks(), чтобы получить список объектов Book в обратном вызове. Красиво и чисто. Ведь у вас есть армия Трибблов, собирающих список и отправляющих его обратно через луч транспортера. :]

Это позволит вам сделать все типы настроек под капотом, не затрагивая клиента. Например, вы можете указать настраиваемый десериализатор JSON, о котором Activity ничего не знает:

val retrofit = Retrofit.Builder()
  .baseUrl("http://www.myexampleurl.com")
  .addConverterFactory(GsonConverterFactory.create())
  .build()

val api = retrofit.create(BooksApi::class.java)

Обратите внимание на использование GsonConverterFactory, работающего за кулисами в качестве десериализатора JSON. С помощью Retrofit вы можете дополнительно настраивать операции с Interceptor и OkHttpClient для управления поведением кэширования и ведения журнала без ведома клиента, что происходит.

Чем меньше каждый объект знает о том, что происходит за кулисами, тем легче вам в будущем будет управлять изменениями в приложении.

Decorator

Шаблон Decorator динамически наделяет объект дополнительными обязанностями, расширяя его функциональность во время выполнения кода. Взгляните на пример ниже:

//1
interface Salad {
  fun getIngredient(): String
}

//2
class PlainSalad : Salad {
  override fun getIngredient(): String {
    return "Arugula & Lettuce"
  }
}

//3
open class SaladDecorator(protected var salad: Salad) : Salad {
  override fun getIngredient(): String {
    return salad.getIngredient()
  }
}

//4
class Cucumber(salad: Salad) : SaladDecorator(salad) {
  override fun getIngredient(): String {
    return salad.getIngredient() + ", Cucumber"
  }
}

//5
class Carrot(salad: Salad) : SaladDecorator(salad) {
  override fun getIngredient(): String {
    return salad.getIngredient() + ", Carrot"
  }
}

Вот что делает приведенный выше код:

  1. Интерфейс Salad помогает узнать ингредиенты.
  2. Каждому Salad нужна основа. Эта основа - Arugula & Lettuce, то есть PlainSalad.
  3. SaladDecorator помогает добавить больше начинки в PlainSalad.
  4. Cucumber наследуется от SaladDecorator.
  5. Carrot наследуется от SaladDecorator.

Используя класс SaladDecorator, вы можете легко расширить свой салат, не изменяя PlainSalad. Вы также можете удалить или добавить любой декоратор салата во время выполнения. Вот как это использовать:

val cucumberSalad = Cucumber(Carrot(PlainSalad()))
print(cucumberSalad.getIngredient()) // Arugula & Lettuce, Carrot, Cucumber
val carrotSalad = Carrot(PlainSalad())
print(carrotSalad.getIngredient()) // Arugula & Lettuce, Carrot

Composite

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

//1
interface Entity {
  fun getEntityName(): String
}

//2
class Team(private val name: String) : Entity {
  override fun getEntityName(): String {
    return name
  }
}

//3
class Raywenderlich(private val name: String) : Entity {
  private val teamList = arrayListOf()

  override fun getEntityName(): String {
    return name + ", " + teamList.map { it.getEntityName() }.joinToString(", ")
  }

  fun addTeamMember(member: Entity) {
    teamList.add(member)
  }
}

В приведенном выше коде у вас есть:

  1. Component, интерфейс Entity в шаблоне Composite.
  2. Класс Team реализует Entity. Это Leaf.
  3. Raywenderlich также реализует интерфейс Entity. Это Composite.

Логически и технически организация, в данном случае Raywenderlich, добавляет сущность в команду (Entity to the Team). Вот как вы это используете:

val composite = Raywenderlich("Ray")
val ericTeamComposite = Raywenderlich("Eric")
val aaqib = Team("Aaqib")
val vijay = Team("Vijay")
ericTeamComposite.addTeamMember(aaqib)
ericTeamComposite.addTeamMember(vijay)
composite.addTeamMember(ericTeamComposite)
print(composite.getEntityName()) // Ray, Eric, Aaqib, Vijay

Поведенческие шаблоны

Будущий вы: «Итак… как мне определить, какой класс за что отвечает?».

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

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

Command

Когда вы заказываете Saag Paneer в индийском ресторане, вы не знаете, какой повар приготовит вам блюдо. Вы просто заказываете блюдо официанту, который размещает заказ на кухне, передавая его доступному повару.

Точно так же шаблон Command позволяет отправлять запросы, не зная получателя. Вы инкапсулируете запрос как объект и отправляете его. Решение о том, как выполнить запрос, - это совершенно отдельный механизм.

EventBus от Greenrobot - это популярная платформа Android, которая поддерживает этот шаблон следующим образом:

Event - это объект стиля Command, который запускается вводом пользователя, данными сервера или чем-либо еще в вашем приложении. Вы можете создавать определенные подклассы, которые также несут данные:

class MySpecificEvent { /* Additional fields if needed */ }

После определения вашего события вы получаете экземпляр EventBus и регистрируете объект в качестве подписчика. Например, если вы зарегистрируете Activity, у вас будет:

override fun onStart() {
  super.onStart()
  EventBus.getDefault().register(this)
}

override fun onStop() {
  super.onStop()
  EventBus.getDefault().unregister(this)
}

Теперь, когда объект является подписчиком, сообщите ему, на какой тип события подписаться и что он должен делать при его получении:

@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: MySpecificEvent?) {
  /* Do something */
}

Наконец, создайте и опубликуйте одно из этих событий на основе ваших критериев:

EventBus.getDefault().post(MySpecificEvent())

Поскольку большая часть этого шаблона творит чудеса во время выполнения, в будущем у вас могут возникнуть небольшие проблемы с отслеживанием этого шаблона, если у вас нет хорошего покрытия тестами. Тем не менее, хорошо продуманный поток команд уравновешивает и делает шаблон хорошо читаемым, и за ним будет позже легко следить.

Observer

Шаблон Observer определяет взаимозависимость между объектами. Когда один объект меняет состояние, его созависимые получают уведомление и обновляются автоматически.

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

Первоначально он был популяризирован фреймворком RxAndroid, также известным как Reactive Android. Эта библиотека позволяет реализовать этот шаблон во всем приложении:

apiService.getData(someData)
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe (/* an Observer */)

Короче говоря, вы определяете объекты Observable, которые будут выдавать значения. Эти значения могут выдаваться все сразу, как непрерывный поток или с любой скоростью и продолжительностью.

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

Совсем недавно Android также представил собственный способ реализации этого шаблона через LiveData. Вы можете узнать больше об этой теме здесь.

Strategy

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

// 1
interface TransportTypeStrategy {
  fun travelMode(): String
}

// 2
class Car : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Road"
  }
}

class Ship : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Sea"
  }
}

class Aeroplane : TransportTypeStrategy {
  override fun travelMode(): String {
    return "Air"
  }
}

// 3
class TravellingClient(var strategy: TransportTypeStrategy) {
  fun update(strategy: TransportTypeStrategy) {
    this.strategy = strategy
  }

  fun howToTravel(): String {
    return "Travel by ${strategy.travelMode()}"
  }
}

Пойдем по коду:

  1. Интерфейс TransportTypeStrategy имеет общий тип для других стратегий, поэтому его можно менять местами во время выполнения.
  2. Все конкретные классы соответствуют TransportTypeStrategy.
  3. TravellingClient составляет стратегию и использует ее функции внутри функций, доступных на стороне клиента.

Вот как это использовать:

val travelClient = TravellingClient(Aeroplane())
print(travelClient.howToTravel()) // Travel by Air
// Change the Strategy to Ship
travelClient.update(Ship())
print(travelClient.howToTravel()) // Travel by Sea

State

В шаблоне State состояние объекта соответственно изменяет свое поведение при изменении внутреннего состояния объекта. Взгляните на следующие фрагменты:

// 1
interface PrinterState {
  fun print()
}

// 2
class Ready : PrinterState {
  override fun print() {
    print("Printed Successfully.")
  }
}

// 3
class NoInk : PrinterState {
  override fun print() {
    print("Printer doesn't have ink.")
  }
}

// 4
class Printer() {
  private val noInk = NoInk()
  private val ready = Ready()
  private var state: PrinterState
  private var ink = 2

  init {
    state = ready
  }

  private fun setPrinterState(state: PrinterState) {
    this.state = state
  }

  // 5
  fun startPrinting() {
    ink--
    if (ink >= 0) {
      setPrinterState(ready)
    } else {
      setPrinterState(noInk)
    }
    state.print()
  }

  // 6
  fun installInk() {
    ink = 2
    print("Ink installed.")
  }
}

Разберем код:

  1. PrinterState определяет состояния принтера.
  2. Ready - это конкретный класс, реализующий PrinterState для определения состояния готовности принтера.
  3. NoInk - это конкретный класс, реализующий PrinterState для определения того, что в принтере нет чернил.
  4. Обработчик Printer выполняет всю печать.
  5. startPrinting начинает печать.
  6. installInk устанавливает чернила.

Вот как это использовать:

val printing = Printer()
printing.startPrinting() // Printed Successfully.
printing.startPrinting() // Printed Successfully.
printing.startPrinting() // Printer doesn't have ink.
printing.installInk() // Ink installed.
printing.startPrinting() // Printed Successfully.    

Итак, вы создаете объект класса Printer для печати. Класс Printer обрабатывает все состояния принтера внутри. Он либо в состоянии Ready, либо в состоянии NoInk.

Архитектура приложений

Будущий Вы: «Может ли в какой-то момент появиться требование, по которому мне придется изменять или внедрять новые функции в проект?».

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

Проще говоря, архитектура относится к общей организации вашего кода, например:

  1. Обязанности для каждого класса
  2. Организация папок
  3. Структура кода: сетевые вызовы, отзывы, ошибки.

Типы архитектур приложений

Существует множество архитектур приложений, используемых для создания надежных и поддерживаемых кодовых баз, но в этой статье вы узнаете самые популярные из них:

  • Model View Controller
  • Model View ViewModel
  • Clean Architecture

Model View Controller

Model View Controller, или MVC, относится к одному из самых популярных архитектурных шаблонов, от которого происходят многие другие. На Android особенно легко настроить свой проект на MVC. Его название относится к трем способам классификации классов в вашем коде:

  • Model: ваши классы данных. Если у вас есть объекты User или Product, они моделируют реальный мир.
    В Kotlin мы используем классы данных, а в Java они назывались POJO (Plain Old Java Object).
  • View: ваши визуальные классы. Все, что видит пользователь, попадает в эту категорию. В случае Android это в основном XML, хотя вы также можете создавать вью в коде, а с введением компоновки у вас появятся другие типы вью.
  • Controller: это клей между ними. Он обновляет вью, принимает вводимые пользователем данные и вносит изменения в модель.

Разделение вашего кода на эти три категории будет иметь большое значение для его разделения и повторного использования.

В конце концов, клиент попросит Будущего Вас добавить в приложение новый экран, который использует его существующие данные. Следование парадигме MVC означает, что Будущий Вы сможет легко повторно использовать те же модели и изменять только вью.

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

Кроме того, перенос как можно большего количества логики макета и ресурсов в Android XML сохраняет ваш View layer чистым и аккуратным. Круто!

Возможно, вам время от времени придется рисовать на Kotlin. В этом случае это поможет отделить операции рисования от классов активности и фрагментов.

Со временем вам станет проще принимать архитектурные решения с помощью MVC и Future. Вам будет легче решать проблемы по мере их возникновения.

Model View ViewModel

Этот архитектурный шаблон с довольно запутанным названием похож на шаблон MVC. Компоненты Model и View такие же.

Объект ViewModel является связующим звеном между слоями model и view layer, но работает иначе, чем компонент Controller. Вместо этого он предоставляет команды для вью и привязывает вью к модели. Когда модель обновляется, соответствующие вью обновляются через связывание данных.

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

Паттерн MVVM набирает популярность, но по-прежнему является относительно недавним дополнением к библиотеке паттернов. Google теперь рекомендует этот шаблон как часть своей библиотеки компонентов архитектуры.

Clean Architecture

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

MVC и MVVM существуют в чистой архитектуре на уровне внешнего представления и пользовательского интерфейса.
Вы можете найти исходное определение чистой архитектуры в этой статье. Доступно множество примеров чистой архитектуры для Android, в том числе «Архитектура Android… Эволюция».

Оригинал статьи.

Содержание