Architecture

https://developer.android.com/topic/architecture/intro
https://d.android.com/jetpack/guide
https://developer.android.com/topic/architecture/ui-layer
https://developer.android.com/topic/architecture/ui-layer/events
https://developer.android.com/topic/architecture/ui-layer/stateholders
https://developer.android.com/topic/architecture/ui-layer/state-production
https://developer.android.com/topic/architecture/domain-layer
https://developer.android.com/topic/architecture/data-layer
https://developer.android.com/topic/architecture/data-layer/offline-first
https://developer.android.com/topic/architecture/recommendations
https://developer.android.com/topic/modularization
https://developer.android.com/topic/modularization/patterns
10.03.2021https://habr.com/ru/company/itelma/blog/546372
23.07.2024https://apptractor.ru/develop/mvi-v-eventbrite.html
13.08.2023https://apptractor.ru/info/articles/chto-takoe-mvvm-arhitektura.html
Структура типов

class

Шаблон или схема для создания объектов. Класс определяет атрибуты (состояние) и методы (поведение), которые объекты этого класса будут иметь.

class Car(val make: String, val model: String) {
    fun drive() {
        println("Driving $make $model")
    }
}

abstract class

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

// Абстрактный класс
abstract class Animal {
    // Абстрактный метод
    abstract fun makeSound()

    // Метод с реализацией
    fun sleep() {
        println("This animal is sleeping")
    }
}

// Конкретный класс, который наследует абстрактный класс
class Dog: Animal() {
    override fun makeSound() {
        println("Woof")
    }
}

// Использование
fun main() {
    val myDog = Dog()
    myDog.makeSound()  // Выведет: Woof
    myDog.sleep()      // Выведет: This animal is sleeping
}

interface

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

interface Drivable {
    fun start()
    fun stop()
}

class Car : Drivable {
    override fun start() {
        println("Car is starting")
    }

    override fun stop() {
        println("Car is stopping")
    }
}

Aggregation

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

// Пример: класс Library может содержать несколько объектов класса Book, но книги могут существовать и без библиотеки.

class Book(val title: String)

class Library(val name: String) {
    private val books = mutableListOf<Book>()

    fun addBook(book: Book) {
        books.add(book)
    }

    fun getBooks(): List<Book> {
        return books
    }
}

fun main() {
    val book1 = Book("1984")
    val book2 = Book("Brave New World")

    val library = Library("City Library")
    library.addBook(book1)
    library.addBook(book2)

    println(library.getBooks().map { it.title })  // Output: [1984, Brave New World]
}

method signature

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

fun calculate(a: Int, b: Double) // Сигнатура метода — это calculate(Int, Double).
Clean Architecture

Архитектура, написанная с заботой. Код разделен на слои, по структуре похоже на лук, с одним правилом зависимости: внутренний уровень не должен зависеть от каких-либо внешних уровней. Зависимости должны указываться внутри каждого уровня, чтобы не было зависимостей между уровнями (слоями).

• облегчить дальнейшую модификацию.

• высокий уровень абстракции.

• слабая связанность между частями кода.

• легкая тестируемость.

DataДанные приложения.
DomainБизнес-логика. Дополнительный слой маппинга данных.
PresentationПользовательский интерфейс.
RepositoryОбъект предоставляющий доступ к данным с возможностью выбора источника данных в зависимости от условий.
InteractorРеализует сценарии использования бизнес-объектов.
UseCaseДействие, которое может совершить пользователь.
EntitiesБизнес-объекты приложения.
MVVM

Model-View-ViewModel. Архитектурный шаблон, используемый в разработке программного обеспечения для разделения пользовательского интерфейса (UI) от бизнес-логики и данных. ViewModel ничего не знает про View. Unidirectional data flow.

Model отвечает за представление данных и бизнес-логику приложения. Модель может включать в себя операции с данными, хранение информации и управление состоянием приложения.

View отвечает за отображение данных и взаимодействие с пользователем. Это компонент, с которым пользователь взаимодействует, и он визуализирует данные, предоставляемые ViewModel.

ViewModel служит посредником между Model и View. ViewModel преобразует данные из Model в формат, который может быть легко отображен в View, и обрабатывает пользовательские действия, перенаправляя их в Model. ViewModel также позволяет реализовать биндинг (связывание) данных между Model и View.

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

Преимущества:

• разделение ответственности: MVVM четко разделяет бизнес-логику (Model) от представления (View) и управления представлением (ViewModel). Это способствует улучшению читаемости кода и упрощает поддержку и развитие приложения.

• тестирование: MVVM упрощает тестирование, так как ViewModel может быть отдельно протестирована, и ее можно тестировать независимо от View. Это делает юнит-тестирование более эффективным и позволяет легче обнаруживать и исправлять ошибки.

• биндинг данных: MVVM позволяет использовать data binding между Model и View через ViewModel. Это позволяет автоматически обновлять пользовательский интерфейс при изменении данных в Model, что упрощает синхронизацию данных и представления.

• масштабируемость: Разделение компонентов MVVM делает приложение более масштабируемым. Разработчики могут работать над разными частями приложения независимо, что позволяет параллельное развитие.

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

• улучшенный UX-дизайн: MVVM позволяет разделить дизайн пользовательского интерфейса и логику взаимодействия с данными. Это улучшает процесс совместной работы дизайнеров и разработчиков, позволяя им работать над интерфейсом независимо.

• использование реактивных подходов: MVVM хорошо сочетается с реактивным программированием, позволяя более эффективно реагировать на изменения данных и событий в приложении.

Недостатки:

• комплексность: Внедрение этого шаблона может потребовать больше времени и усилий на начальном этапе разработки из-за необходимости создания ViewModel для каждого View. Это может добавить сложности, особенно для простых приложений.

• увеличение количества классов: Использование шаблона может привести к увеличению количества классов в проекте, особенно если приложение имеет много экранов и компонентов. Это может сделать проект более громоздким и усложнить его обслуживание.

• сложности при обработке сложной бизнес-логики: Если бизнес-логика приложения довольно сложная и требует прямого взаимодействия между Model и View, MVVM может создать дополнительный уровень абстракции (ViewModel), что может усложнить обработку таких сценариев.

MVP

Model-View-Presenter. Взаимодействие Presenter и View построено через интерфейс. View знает про Presenter и наоборот. Two-way binding.

Model представляет данные и бизнес-логику приложения.

View отвечает за отображение данных и взаимодействие с пользователем.

Presenter извлекает данные из модели и уведомляет View через интерфейс, презентер управляет состоянием представления и выполняет действия в соответствии с уведомлениями пользователя из View.

• взаимодействие между View-Presenter и Presenter-Model происходит через интерфейс.

• один класс презентера управляет одним представлением

• модель и view ничего не знают о существовании друг друга

MVI

Model-View-Intent. Intent обрабатывает события от View и передает их в Model, она обрабатывает события и возвращает новую, готовую для отображения модель, которую отобразит View.

Model представляет данные и бизнес-логику приложения. В MVI модель неизменяема и представляет собой текущее состояние приложения.

View отвечает за отрисовку пользовательского интерфейса и реакцию на ввод данных пользователем. Однако, в отличие от MVVM и MVC, представление в MVI является пассивным компонентом. Он не взаимодействует с моделью напрямую и не принимает решений на основе данных. Вместо этого он получает обновления состояния и пользовательские намерения от ViewModel.

Intent представляет собой действия пользователя или события, происходящие в пользовательском интерфейсе, такие как нажатие кнопки или ввод текста. В MVI эти намерения перехватываются представлением и отправляются во ViewModel для обработки.

ViewModel в MVI она отвечает за управление состоянием приложения и бизнес-логикой. Она получает пользовательские намерения от представления, обрабатывает их и соответствующим образом обновляет модель. Затем ViewModel выдает новое состояние, которое View наблюдает и отображает.
• однонаправленность - с данными работает только одна сущность, и мы знаем, кто изменяет данные, зачем и почему.
• неизменяемость состояния - новое состояние складывается из предыдущего и какого-то действия. Изменить данные мы не можем, можем только получить новые.
• удобство логирования и отладки - легко воспроизвести, где была ошибка, и собрать все условия (отследить в crashlytics текущий state и тот, что был до краша.
• удобно работать с jetpack compose.

Преимущества:

• хорошо интегрируется с декларативными UI фреймворками такими как Compose и SwiftUI.

• один единственный стейт, который рисуется на вьюшке.

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

Недостатки:

• бойлерплейт. стейт может быть большим на сложном экране, например, может быть 15 полей в стейте.

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

Reducer

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

Middleware

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

UDF

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

Один источник истины. Все состояние приложения хранится в одном месте, обычно в модели или хранилище состояния.

Изменение состояния. Для изменения состояния приложения используются только действия (actions) или события, которые обрабатываются функциями или reducers, изменяющими состояние.

Обновление пользовательского интерфейса. Изменения состояния передаются в пользовательский интерфейс через связывание данных. Это обеспечивает синхронизацию UI с текущим состоянием.

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

// Модель (Model)
data class User(val name: String, val age: Int)

// ViewModel
class UserViewModel: ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> get() = _user

    fun updateUser(name: String, age: Int) {
        _user.value = User(name, age)
    }
}

// Activity/Fragment
class UserActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)

        viewModel.user.observe(this, Observer { user ->
            // Обновление UI на основе новых данных
            userNameTextView.text = user.name
            userAgeTextView.text = user.age.toString()
        })

        updateButton.setOnClickListener {
            viewModel.updateUser("John Doe", 30)
        }
    }
}
ООП

Object-Oriented Programming (OOP). Объектно-ориентированное программирование (ООП) - методология программирования основанная на представлении программы в виде совокупности объектов каждый из которых является экземпляром определенного класса а классы образуют иерархию наследования.

Encapsulation

Инкапсуляция позволяет скрывать внутренние детали реализации объекта и предоставлять доступ к данным через методы. Это означает, что объект контролирует доступ к своим данным и предотвращает их неконтролируемое изменение.

// В данном примере name скрыт от прямого доступа извне и доступен только через методы setName и getName.
class Person {
    private var name: String = ""
    
    fun setName(name: String) {
        this.name = name
    }
    
    fun getName(): String {
        return name
    }
}

Polymorphism

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

open class Animal {
    open fun makeSound() {
        println("Какой-то звук")
    }
}

class Dog: Animal() {
    override fun makeSound() {
        println("Гав")
    }
}

class Cat: Animal() {
    override fun makeSound() {
        println("Мяу")
    }
}

fun main() {
    val animals: List<Animal> = listOf(Dog(), Cat())
    animals.forEach { it.makeSound() }
}

Inheritance

Наследование позволяет создавать новые классы на основе существующих. Новый класс (производный класс) наследует свойства и методы базового класса, что позволяет повторно использовать код и создавать иерархию классов.

open class Animal {
    fun eat() {
        println("Жрет")
    }
}

class Dog: Animal() {
    fun bark() {
        println("Гавкает")
    }
}

Abstraction

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

// В данном примере Shape – абстрактный класс с абстрактным методом draw, который реализуется в конкретных классах Circle и Square.
abstract class Shape {
    abstract fun draw()
}

class Circle: Shape() {
    override fun draw() {
        println("Drawing a Circle")
    }
}

class Square: Shape() {
    override fun draw() {
        println("Drawing a Square")
    }
}
Development Principles

SOLID

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

Single ResponsibilityПринцип Единственной ОтветственностиКаждый класс или метод должны иметь одну ответственность.
Не стоит нагружать классы большой логикой.
Пример: RecyclerView:
ItemAnimator LayoutManager Adapter DiffUtil.
Open-ClosedПринцип Открытости/ЗакрытостиКлассы должны быть открыты для расширения, но закрыты для модификации.
Поведение класса можно расширить без изменения существующего кода.
Liskov SubstitutionПринцип Подстановки ЛисковОбъекты базового класса должны быть заменимы объектами его подклассов без изменения корректности программы.
Подклассы должны поддерживать контракт базового класса.
Interface SegregationПринцип Разделения ИнтерфейсовИнтерфейсы должны быть специфичными и не содержать методов, которые не нужны реализациям.
Это предотвращает ситуацию, когда класс вынужден реализовывать методы, которые ему не нужны.
Dependency Inversion Принцип Инверсии ЗависимостейМодули высокого уровня не должны зависеть от модулей низкого уровня.
Оба типа модулей должны зависеть от абстракций (интерфейса).
Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.

DRY

Don't Repeat Yourself. Не повторяйся. Дублирование кода – пустая трата времени и ресурсов. Вам придется поддерживать одну и ту же логику и тестировать код сразу в двух местах, причем если вы измените код в одном месте его нужно будет изменить и в другом. В большинстве случаев дублирование кода происходит из-за незнания системы. Прежде чем что-либо писать, проявите прагматизм: осмотритесь. Возможно, эта функция где-то реализована. Возможно эта бизнес-логика существует в другом месте. Повторное использование кода – всегда разумное решение.

KISS

Keep It Simple, Stupid. Будь проще. Простые системы будут работать лучше и надежнее сложных. Не придумывайте к задаче более сложного решения, чем ей требуется. Иногда самое разумное решение оказывается и самым простым. Написание производительного, эффективного и простого кода – это прекрасно. Одна из самых распространенных ошибок нашего времени – использование новых инструментов исключительно из-за того, что они блестят. Разработчиков следует мотивировать использовать новейшие технологии не потому, что они новые, а потому что они подходят для работы.

YAGNI

You Aren’t Gonna Need It. Вам это не понадобится. Если пишете код, будьте уверены, что он понадобится. Не пишите код, если думаете, что он пригодится позже. Принцип применим при рефакторинге. Если вы занимаетесь рефакторингом метода, класса или файла, не бойтесь удалять лишние методы. Даже если раньше они были полезны – теперь они не нужны. Может наступить день, когда они снова понадобятся – тогда вы сможете воспользоваться git-репозиторием, чтобы воскресить их из мертвых.

BDUF

Big Design Up Front. Глобальное проектирование прежде всего. Прежде чем переходить к реализации, убедитесь, что все хорошо продумано. Составив план, вы избавите себя от необходимости раз за разом начинать с нуля.

APO

Avoid Premature Optimization. Избегайте преждевременной оптимизации. Эта практика побуждает разработчиков оптимизировать код до того, как необходимость этой оптимизации будет доказана. Прежде чем вы погрузитесь в детали реализации, убедитесь, что эти оптимизации действительно полезны. Пример – масштабирование. Вы не станете покупать 40 серверов из предположения, что ваше новое приложение станет очень популярным. Вы будете добавлять серверы по мере необходимости. Преждевременная оптимизация может привести к задержкам в коде и, следовательно, увеличит затраты времени на вывод функций на рынок.

Occam's razor

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

Сlasses and objects relationships

association

Ассоциация. Отношение, при котором объекты одного типа неким образом связаны с объектами другого типа. Например объект одного типа содержит или использует объект другого типа. Например, игрок играет в определенной команде:

class Team

class Player {
    val team: Team
}

composition

Композиция определяет отношение HAS A, то есть отношение "имеет". Например, в класс автомобиля содержит объект класса электрического двигателя. При этом класс автомобиля полностью управляет жизненным циклом объекта двигателя. При уничтожении объекта автомобиля в области памяти вместе с ним будет уничтожен и объект двигателя. И в этом плане объект автомобиля является главным, а объект двигателя - зависимой.

class ElectricEngine

class Car {
    val engine: ElectricEngine = ElectricEngine()
}

aggregation

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

abstract class Engine

class Car(
    var engine: Engine
)

Design Patterns

Паттерны проектирования необходимы для быстрого решения типовых задач в программировании. Паттерны бывают 3 разновидностей:
• архитектурные паттерны. Шаблоны высшего уровня. описывают структурную схему программной системы в целом. В этой схеме располагаются отдельные подсистемы и определяются отношения между ними. Пример: MVP.
• паттерны проектирования. Они описывают схемы детализации программных подсистем и их отношений между собой. Такие паттерны никак не влияют на структуру программной системы в целом и не зависят от использования языка программирования. Пример: Adapter, Singleton.
• идиомы. Паттерны низкого уровня. Реализация той или иной проблемы с учётом специфики соответствующего языка программирования.

Паттерны проектирования:
• порождающие. Пример: Factory, Singleton.
• структурные. Пример: Decarator, Adapter.
• поведенческие. Пример: Observer, Strategy.

Adapter

Позволяет объектам с несовместимыми интерфейсами работать вместе. Адаптер предусматривает создание класса-оболочки с требуемым интерфейсом.

/**
 * Совместимый интерфейс и его реализация
 */
interface NickName {
    fun getNickName(person: Person): String
}
class NickNameImpl: NickName {
    override fun getNickName(person: Person): String = person.name
}

/**
 * Несовместимый интерфейс и его реализация
 */
interface FullName {
    fun getFullName(person: Person): String
}
class FullNameImp: FullName {
    override fun getFullName(person: Person): String = "${person.name} ${person.family}"
}

class Client(
    private val nickName: NickName
) {
    private val person = Person("John", "Marston")

    val name: String
        get() = nickName.getNickName(person)
}

class Adapter(
    private val fullName: FullName
): NickName {

    override fun getNickName(person: Person): String {
        return fullName.getFullName(person)
    }
}

val nickName: NickName = NickNameImpl()
val fullName: FullName = FullNameImp()
val adapter: NickName = Adapter(fullName)

val clientTarget = Client(nickName)
println(clientTarget.name) // output: John
val clientAdapter = Client(adapter)
println(clientAdapter.name) // output: John Marston

Decorator

Динамически добавляет новую функциональность объекту, используя класс-обертку. Пример: при добавлении нового ключа в HashMap, сообщать об этом.

interface Name {
    fun getName(firstName: String): String
}

class SimpleName: Name {
    override fun getName(firstName: String): String {
        return firstName
    }
}

open class NameDecorator(
    protected var name: Name
): Name {
    override fun getName(firstName: String): String {
        return name.getName(firstName)
    }
}

class FullName(
    name: Name
): NameDecorator(name) {

    val lastName: String = "Marston"

    override fun getName(firstName: String): String {
        return name.getName(firstName) + " " + lastName
    }
}

val firstName: String = "John"val name: Name = SimpleName()
println(name.getName(firstName)) // output: John

val nameDecorator: Name = NameDecorator(name)
println(nameDecorator.getName(firstName)) // output: John

val fullName: Name = FullName(name)
println(fullName.getName(firstName)) // output: John Marston

Facade

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

interface Name {
    fun getName(): String
}
class NameImp: Name {
    override fun getName(): String = "John"
}

interface FullName {
    fun getFullName(): String
}
class FullNameImp: FullName {
    override fun getFullName(): String = "John Marston"
}

class FacadeName(
    private val name: Name,
    private val fullName: FullName
) {
    fun getName(): String = name.getName()

    fun getFullName(): String = fullName.getFullName()
}

val name: Name = NameImp()
val fullName: FullName = FullNameImp()
val facade = FacadeName(name, fullName)
println(facade.getName()) // output: John
println(facade.getFullName()) // output: John Marston

Composite

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

open class Person(
    private val name: String
) {
    open fun getName(): String = name
}

class John: Person("John")
class Arthur: Person("Arthur")

open class Composite(
    private val name: String
): Person(name) {

    private val persons: MutableList<Person> = mutableListOf()

    fun add(person: Person) {
        persons.add(person)
    }

    override fun getName(): String {
        return name + ", " + persons.joinToString(", ") { it.getName() }
    }
}

val composite = Composite("Sadie")
composite.add(John())
composite.add(Arthur())
println(composite.getName()) // output: Sadie, John, Arthur

Singleton

Когда нам необходим один экземппляр объекта и глобальная точка доступа к нему. Пример: класс Application.

Android Architecture. Вопросы на собесе
  1. Перечисли слои чистой архитектуры в контексте Android?
  1. Различие Interactor и UseCase?
  1. Что входит в сигнатуру метода?
  1. Расскажи про паттерны банды четырех?
  1. Преимущества MVI?

    Преимущества MVI включают единый источник истины, который упрощает управление состоянием и отладку, а также четкое разделение на Model, View и Intent с односторонним потоком данных.

  1. Где в clean-архитектуре используется принцип Dependency Inversion из SOLID?

    В Clean Architecture принцип Dependency Inversion помогает отделить бизнес-логику от деталей реализации, определяя интерфейсы в бизнес-логике и внедряя их реализации через Dependency Injection. Это улучшает гибкость и тестируемость, позволяя легко заменять реализации без изменения бизнес-логики.

  1. Что такое UDF?

    Unidirectional Data Flow (UDF) — это архитектурный паттерн, при котором данные в приложении передаются и изменяются в одном направлении, обеспечивая предсказуемость и упрощая управление состоянием.

  1. Что такое агрегация?

    Это отношение между объектами, где один объект содержит другой, но оба могут существовать независимо друг от друга.

  1. Разница между наследованием и композицией? abstract class vs interface?

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

  1. Как MVVM соотносится с Clean Architecture?

    Model ↔ Data. View ↔ Presentation. ViewModel ↔ Domain. Model будет в Presentation-слое.

  1. Где в Android нарушается 1 принцип SOLID?

    Бизнес-логика в Activity/Fragment. Класс ListView

  1. Какой компонент MVI отвечает за определение нового состояния на основе текущего состояния и действия?

    ViewModel

    Middleware

    Reducer

    Controller

  1. Какой компонент MVI отвечает за обработку побочных эффектов, таких как сетевые запросы?

    SideEffectHandler

    Interactor

    Reducer

    Middleware

  1. Какой из следующих компонентов используется для отображения данных в MVVM архитектуре?

    Controller

    Model

    View

    ViewModel

  1. Какой компонент отвечает за обеспечение реактивного обновления данных в MVVM?

    DataSource

    Repository

    ViewModel

    LiveData