Architecture
10.03.2021 | https://habr.com/ru/company/itelma/blog/546372 |
23.07.2024 | https://apptractor.ru/develop/mvi-v-eventbrite.html |
13.08.2023 | https://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 и тот, что был до краша.
• Удобно работать с 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")
}
}
SOLID
Пять основополагающих принципов ООП, разработанных для повышения гибкости, расширяемости и удобства поддержки кода.
Single Responsibility
Принцип Единственной Ответственности. Каждый класс или метод должны иметь одну ответственность. Не стоит нагружать классы большой логикой.
Open-Closed
Принцип Открытости/Закрытости. Классы должны быть открыты для расширения, но закрыты для модификации. Поведение класса можно расширить без изменения существующего кода.
Liskov Substitution
Принцип Подстановки Лисков. Объекты базового класса должны быть заменимы объектами его подклассов без изменения корректности программы. Подклассы должны поддерживать контракт базового класса.
Interface Segregation
Принцип Разделения Интерфейсов. Интерфейсы должны быть специфичными и не содержать методов, которые не нужны реализациям. Это предотвращает ситуацию, когда класс вынужден реализовывать методы, которые ему не нужны.
Dependency Inversion
Принцип Инверсии Зависимостей. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций (интерфейса). Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.
DRY
Don't Repeat Yourself. Не повторяй код. Цель — уменьшить дублирование информации или логики в системе, что способствует более легкой поддержке и масштабируемости кода. Пример нарушения DRY: если один и тот же код написан в нескольких местах приложения. Чтобы соблюсти DRY, нужно вынести повторяющийся код в общую функцию, которая будет вызываться при необходимости.
KISS
Keep It Simple, Stupid. Предполагает, что системы и код должны быть как можно проще. Основная идея — избегать излишней сложности в коде, поскольку простой код легче читать, поддерживать и тестировать. Пример: вместо написания сложного алгоритма с множеством вложенных условий, лучше разложить задачу на более простые и независимые части, сохраняя код компактным и понятным.
YAGNI
You Aren’t Gonna Need It. Не стоит добавлять функционал в код, который на текущий момент не нужен. Основная идея — писать только тот код, который решает актуальные задачи, а не готовить приложение к гипотетическим требованиям в будущем. Пример: при разработке приложения не стоит сразу реализовывать сложные опции для настройки, если в данный момент достаточно базового функционала.
С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
.
Trunk Based Development
Метод разработки программного обеспечения, при котором все разработчики работают в одной основной ветке кода, часто называемой «trunk» или «main». Основная идея TBD заключается в том, чтобы минимизировать количество веток и слияний, что способствует более быстрому и стабильному процессу разработки.
• Работа в одной ветке: Все разработчики коммитят изменения в одну общую ветку (обычно main или trunk), что позволяет избежать проблем с слиянием и конфликтами, которые часто возникают при работе с множеством веток.
• Частые коммиты: Разработчики должны часто коммитить свои изменения, что уменьшает риск возникновения больших и сложных изменений, которые трудно тестировать и интегрировать.
• Короткие циклы разработки: Изменения, как правило, небольшие и должны быть завершены в течение одного-двух дней. Это позволяет быстро реагировать на изменения требований и быстрее выпускать новые версии продукта.
• Постоянное интегрирование: Важно, чтобы код был всегда в рабочем состоянии, что подразумевает использование автоматического тестирования и сборки для обеспечения качества кода.
• Код-ревью и парное программирование: Чаще всего используются практики код-ревью и парного программирования, чтобы улучшить качество кода и помочь разработчикам лучше понимать изменения друг друга.
Вопросы на собесе (70)
Clean (8)
- Что такое чистая архитектура?
Чистая архитектура — это подход к проектированию программного обеспечения, который разделяет код на слои, минимизируя зависимость между ними. Это позволяет повысить тестируемость, гибкость и поддержку приложения, отделяя бизнес-логику от пользовательского интерфейса и инфраструктуры.
- Перечисли слои чистой архитектуры в контексте Android?
•
Presentation
UI и логика представления (Activity, Fragment, ViewModel).•
Domain
Бизнес-логика и правила (UseCase, Interactor).•
Data
Работа с данными (репозитории, источники данных, API).
- Какие модули в Clean являются верхними и нижними?
• Верхние модули: Presentation (UI, ViewModels) и Domain (Use Cases, Entities), которые отвечают за бизнес-логику.
• Нижние модули: Data (репозитории, источники данных) и Infrastructure (внешние сервисы, базы данных), которые взаимодействуют с внешними системами и обеспечивают хранение и доступ к данным.
- Что такое Entity в Clean?
Объект, представляющий бизнес-логику и данные приложения. Он изолирует бизнес-правила от инфраструктуры и используется в UseCases. Entities помогают улучшить тестируемость и поддержку, позволяя сосредоточиться на основных бизнес-процессах.
- Где в clean-архитектуре используется принцип Dependency Inversion из SOLID?
В Clean Architecture принцип Dependency Inversion помогает отделить бизнес-логику от деталей реализации, определяя интерфейсы в бизнес-логике и внедряя их реализации через Dependency Injection. Это улучшает гибкость и тестируемость, позволяя легко заменять реализации без изменения бизнес-логики.
- Недостатки чистой архитектуры, в каких проектах она не нужна?
• В маленьких проектах или прототипах, где важна скорость разработки, а не гибкость и расширяемость.
• В проектах с очень узким функционалом, где количество логики невелико, и разделение на слои только усложнит архитектуру.
- Разница между Domain и Data слоями?
Domain слой отвечает за бизнес-логику приложения и включает в себя модели, сервисы и интерфейсы, а Data слой занимается управлением данными, включая их получение, хранение и передачу, работая с внешними источниками данных, такими как базы данных или API.
- В каком слое размещается Mapper в Clean?
В domain или data слоях, в зависимости от его назначения:
• Если маппер преобразует данные между слоями data и domain (например, из DTO в Entity), его лучше поместить в domain слой, так как он отвечает за преобразование данных в формат, удобный для бизнес-логики.
• Если маппер используется для преобразования данных только внутри data слоя (например, преобразует данные из локальной БД в формат DTO), его следует поместить в data слой.
- Что такое чистая архитектура?
MVVM (8)
- Что такое архитектура MVVM?
Архитектурный паттерн, разделяющий логику приложения на три компонента: Model для управления данными, View для отображения данных и ViewModel для связывания Model с View. ViewModel содержит логику UI и данные, но не зависит от View, что позволяет легче тестировать и переиспользовать код.
- Как в MVVM View получает данные из ViewModel?
Через наблюдаемые объекты
Flow
илиLiveData
.
- Как MVVM соотносится с Clean Architecture?
Model ↔ Data. View ↔ Presentation. ViewModel ↔ Domain. Model будет в Presentation-слое.
- Различие между MVVM и MVP?
В MVVM ViewModel управляет состоянием и данными для View через
Flow
, в то время как в MVP Presenter обрабатывает действия пользователя и обновляет View напрямую.
- На каком слое разместим Mapper?
В Clean на слое Data. В MVVM на ViewModel для UI.
- Какие паттерны проектирования реализует MVVM?
• Наблюдатель (Observer) — ViewModel отслеживает изменения в Model и уведомляет View об этих изменениях.
• Команда (Command) — ViewModel обрабатывает действия пользователя через команды, которые затем изменяют состояние Model.
• Посредник (Mediator) — ViewModel выступает посредником между View и Model, обеспечивая их независимость друг от друга.
- Какой из следующих компонентов используется для отображения данных в MVVM архитектуре?
• Controller
• Model
• View
• ViewModel
- Какой компонент отвечает за обеспечение реактивного обновления данных в MVVM?
• DataSource
• Repository
• ViewModel
• LiveData
- Что такое архитектура MVVM?
MVI (3)
- Преимущества MVI?
Преимущества MVI включают единый источник истины, который упрощает управление состоянием и отладку, а также четкое разделение на Model, View и Intent с односторонним потоком данных.
- Какой компонент MVI отвечает за определение нового состояния на основе текущего состояния и действия?
• ViewModel
• Middleware
• Reducer
• Controller
- Какой компонент MVI отвечает за обработку побочных эффектов, таких как сетевые запросы?
• SideEffectHandler
• Interactor
• Reducer
• Middleware
- Преимущества MVI?
SOLID (11)
- Принципы SOLID?
• S - Принцип Единственной Ответственности. Каждый класс или метод должны иметь одну ответственность.
• O - Принцип Открытости/Закрытости. Классы должны быть открыты для расширения, но закрыты для модификации.
• L - Принцип Подстановки Лисков. Объекты базового класса должны быть заменимы объектами его подклассов без изменения корректности программы.
• I - Принцип Разделения Интерфейсов. Интерфейсы должны быть специфичными и не содержать методов, которые не нужны реализациям.
• D - Принцип Инверсии Зависимостей. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций (интерфейса).
- Где в Android применяется принцип единственной ответственности (S) из SOLID?
UseCase для выполнения одной конкретной бизнес-задачи.
- Где в Android применяется принцип открытости/закрытости (O) из SOLID?
При наследовании компонентов. Например в
RecyclerView
адаптеры можно расширять, добавляя логику для работы с новыми типами данных, но сам классRecyclerView.Adapter
остается неизменным.
- Где в Android применяется принцип подстановки лисков (L) из SOLID?
При создании кастомных
View
. Можно создать класс, который наследуетButton
, добавляя собственные методы, при этом все методы базового класса остаются работоспособными.
- Где в Android применяется принцип разделения интерфейсов (I) из SOLID?
В репозиториях интерфейсы могут быть разделены по действиям (например, UserRepository для работы с пользователями и ProductRepository для продуктов), что предотвращает наличие неиспользуемых методов.
- Где в Android применяется принцип инверсии зависимостей из (D) SOLID?
Бизнес-логика обращается к абстракциям репозитория, а сам репозиторий может использовать разные источники данных (например, локальную БД, API и т.д.). Это также упрощает тестирование, позволяя заменять реальные реализации на заглушки.
- Где в Android нарушается принцип единственной ответственности (S) из SOLID?
• Бизнес-логика в
Activity
/Fragment
.• Класс
ListView
.
- Где в Android SDK нарушается принцип подстановки Лисков (L) из SOLID?
•
SparseArray
иHashMap
:SparseArray
не является полной заменойHashMap
, хотя используется для аналогичных целей. У него другой контракт и ограниченный функционал, что может вести к ошибкам при подстановке.•
AlertDialog
иDialog
:AlertDialog
предполагается как частный случайDialog
, но часто изменяет поведениеDialog
, что нарушает принцип совместимости при подстановке.
- Какой принцип SOLID будет нарушен если в MVVM модель будет знать о конкретной реализации ViewModel?
Будет нарушен принцип инверсии зависимостей D (Dependency Inversion). Этот принцип требует, чтобы высокоуровневые модули (например, ViewModel) не зависели от низкоуровневых модулей (например, Model). Если Model знает о конкретной реализации ViewModel, то нарушается независимость этих уровней.
- Какие классы/интерфейсы Android API нарушают принципы SOLID?
•
AsyncTask
нарушает Single Responsibility принцип, так как одновременно управляет потоками и взаимодействует с UI.•
Context
нарушает Interface Segregation принцип, поскольку содержит слишком много обязанностей, что делает его трудным для замены и тестирования.•
Handler
нарушает Liskov Substitution принцип, так как его сложно правильно заменить или протестировать из-за плотной привязки к главному потоку.
- Соответствует ли Activity букве S ил SOLID?
Нет,
Activity
нарушает принцип S из SOLID, поскольку часто объединяет функционал UI, управления жизненным циклом, навигации и работы с данными. Это приводит к избыточной логике, делаетActivity
трудно тестируемой и затрудняет сопровождение. В идеале,Activity
должна лишь отображать данные и взаимодействовать сViewModel
, которые содержат основную логику, отделяя управление UI от бизнес-логики.
- Принципы SOLID?
ООП (6)
- Какие есть принципы ООП?
• Инкапсуляция.
• Полиморфизм.
• Наследование.
• Абстракиция (выделяют как 4 принцип).
- Что такое инкапсуляция?
Сокрытие внутренней реализации объекта и предоставление доступа к его данным только через методы, что защищает состояние объекта от несанкционированных изменений.
- Что такое полиморфизм?
Способность объектов разных классов реагировать на одни и те же сообщения (методы) по-разному, позволяя использовать интерфейсы и абстрактные классы для создания гибких и расширяемых программных систем.
- Что такое наследование?
Возможность создавать новые классы на основе существующих, унаследовав их свойства и методы, что способствует повторному использованию кода и упрощает его поддержку.
- Что такое абстракция?
Выделение общих свойств и методов объектов, позволяющее сосредоточиться на важных характеристиках, скрывая детали реализации, что упрощает восприятие и использование системы.
- Без какого принципа ООП нельзя реализовать абстрактную фабрику и почему?
Без принципа полиморфизма невозможно реализовать абстрактную фабрику, так как он позволяет работать с объектами через их общие интерфейсы или базовые классы. Это необходимо для создания различных типов объектов через единую фабрику, не уточняя их конкретные классы.
- Какие есть принципы ООП?
Patterns (17)
- Какие есть виды паттернов?
• Порождающие паттерны — отвечают за создание объектов.
• Структурные паттерны — описывают, как классы и объекты взаимодействуют между собой.
• Поведенческие паттерны — определяют алгоритмы и потоки управления.
- Какие есть антипаттерны?
• God Object (Божественный объект) — один объект или класс берет на себя слишком много ответственности, нарушая принцип единственной ответственности (SRP).
• Spaghetti Code (Спагетти-код) — код с плохой структурой, где зависимости между компонентами запутаны, что усложняет чтение и поддержку.
• Golden Hammer (Золотой молоток) — использование одной техники, библиотеки или подхода для решения всех задач, даже когда они не подходят.
• Magic Numbers (Магические числа) — использование «жестко закодированных» чисел или строк без объяснений, что делает код трудно читаемым и поддерживаемым.
- Расскажи про паттерны банды четырех?
Паттерны банды четырех (GoF) — это 23 классических паттерна проектирования, описанных в книге «Design Patterns». Эти паттерны помогают создавать гибкие и масштабируемые приложения. Они делятся на три категории:
• Создающие: Определяют способы создания объектов (например, Singleton, Factory Method).
• Структурные: Описывают, как объекты могут составляться в структуры (например, Adapter, Decorator).
• Поведенческие: Фокусируются на взаимодействии между объектами (например, Observer, Strategy).
- Какие есть поведенческие паттерны?
• Наблюдатель (Observer) — используется в архитектуре, например, в
Flow
иRxJava
.• Команда (Command) — применяется для обработки действий, таких как клики и другие события UI.
• Стратегия (Strategy) — используется для замены поведения объектов в зависимости от контекста, например, для выбора подходящего способа обработки данных или действий.
- Какие есть порождающие паттерны?
• Одиночка (Singleton) — гарантирует создание единственного экземпляра класса, часто используется для работы с синглтон-сервисами, например,
ViewModel
в Android.• Фабричный метод (Factory Method) — предоставляет интерфейс для создания объектов, позволяя подклассам выбирать конкретные классы.
• Строитель (Builder) — упрощает создание сложных объектов пошагово, часто используется для настройки объектов, таких как
AlertDialog
илиNotification
.
- Какие есть структурные паттерны?
• Адаптер (Adapter) — позволяет объектам с несовместимыми интерфейсами работать вместе, часто используется для интеграции разных API в Android.
• Декоратор (Decorator) — добавляет новое поведение объектам динамически, применяется для расширения функциональности, например, при работе с
RecyclerView
.• Фасад (Facade) — предоставляет упрощённый интерфейс для сложных систем или библиотек, упрощая взаимодействие с ними.
- Расскажи про паттернт Adapter?
Cтруктурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. Он действует как посредник, преобразуя интерфейс одного класса в интерфейс, который ожидает другой класс, тем самым позволяя объектам взаимодействовать, несмотря на различия в их API.
- Расскажи про паттерн Bridge?
Cтруктурный паттерн, который разделяет абстракцию и реализацию, позволяя им развиваться независимо друг от друга. Основная цель паттерна — уменьшить количество классов в системе за счет разделения разных аспектов их поведения.
- Расскажи про паттерн Builder?
Порождающий паттерн, который позволяет поэтапно создавать сложные объекты, отделяя процесс их конструирования от конечного представления. Это полезно, когда объект состоит из множества частей или параметров, и процесс его создания требует настройки на каждом шаге.
- Какие примеры паттерна Builder есть в Android?
•
AlertDialog.Builder
•
NotificationCompat.Builder
- Расскажи про паттерн Observer?
Паттерн Observer описывает отношение «один ко многим» между объектами, где один объект (субъект) уведомляет других (наблюдателей) о своих изменениях состояния. Наблюдатели регистрируются у субъекта и получают уведомления о событиях, что позволяет им реагировать на изменения, не создавая жестких зависимостей между ними.
- Расскажи про паттерн Factory method?
Порождающий паттерн, который определяет интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемого объекта. Этот паттерн делегирует логику создания объектов дочерним классам, что делает систему более гибкой.
- Расскажи про паттерн Abstract Factory?
Порождающий паттерн, который позволяет создавать семейства связанных объектов без указания их конкретных классов. Он предоставляет интерфейс для создания различных объектов, но делегирует процесс создания подклассам, тем самым обеспечивая независимость клиентского кода от конкретных типов создаваемых объектов.
- Расскажи про паттерн Chain of Responsebility?
Позволяет передавать запрос последовательно через цепочку обработчиков, где каждый обработчик решает, обработать запрос или передать его дальше по цепочке. Это позволяет разделить логику обработки на отдельные объекты, уменьшая связность.
- Расскажи про паттерн Prototype?
Позволяет создавать новые объекты путем копирования существующих экземпляров (прототипов), вместо создания объектов с нуля. Это полезно, когда процесс создания объектов является дорогостоящим, а копирование — более эффективным способом.
- Расскажи про паттерн Momento?
Поведенческий паттерн, который позволяет сохранять и восстанавливать состояние объекта, не нарушая его инкапсуляцию. Он полезен, когда нужно вернуть объект к предыдущему состоянию, например, при реализации функции «отменить» (undo).
- Расскажи про паттерн DTO?
DTO (Data Transfer Object) — архитектурный паттерн, который используется для передачи данных между слоями приложения или между различными системами. Основная цель DTO — эффективно перемещать данные, избегая избыточной логики в процессе передачи.
- Какие есть виды паттернов?
EventBus (2)
- Что такое EventBus?
Паттерн, позволяющий объектам регистрироваться как слушатели событий, публикуемых другими объектами. При возникновении события EventBus уведомляет всех слушателей, передавая необходимые данные, что упрощает взаимодействие и снижает зависимости между компонентами.
- Почему EventBus считается антипаттерном?
Потому что скрывает зависимости между компонентами, усложняя отладку и тестирование. Также он может снижать производительность из-за трудностей в отслеживании потоков событий в крупных приложениях.
- Что такое EventBus?
Single Activity (2)
- Какие есть преимущества у SingleActivity?
• Упрощенная навигация.
• Уменьшение проблем с жизненным циклом.
- Какие есть минусы у SingleActivity?
• Сложность управления навигацией: При использовании одного активити может усложниться управление навигацией, особенно с учетом глубоких вложенных фрагментов и сложных переходов.
• Увеличение объема кода: Логика навигации и управления состоянием может привести к увеличению объема кода в одной активности, что усложняет поддержку и тестирование.
• Трудности с восстановлением состояния: Сложнее управлять состоянием фрагментов при повороте экрана или изменении конфигурации, так как вся логика сосредоточена в одном месте.
- Какие есть преимущества у SingleActivity?
Другие (14)
- Что нравится в Android-разработке?
• Гибкость работы с архитектурами.
• Jetpack Compose для построения UI.
• Поддержка Kotlin и Coroutines.
- Что не нравится в Android-разработке?
• Фрагментация устройств.
• Медленное время сборки.
• Устаревшие библиотеки и технологии.
- Какие есть архитектуры в Android?
• MVVM
• MVI
• MVP
- Для чего используется Interactor?
Interactor
используется в архитектуре приложений для отделения бизнес-логики от представления. Он служит промежуточным слоем, который обрабатывает запросы от UI, выполняет необходимые операции (например, взаимодействие с репозиториями) и возвращает результаты. Это упрощает тестирование и поддержку кода, а также способствует более чистой архитектуре.
- Различие между Interactor и UseCase?
Interactor
может включать несколькоUseCase
, аUseCase
описывает отдельные бизнес-сценарии.
- Что входит в сигнатуру метода?
Его имя и список параметров (их типы и порядок).
- Что такое UDF?
Unidirectional Data Flow (UDF) — это архитектурный паттерн, при котором данные в приложении передаются и изменяются в одном направлении, обеспечивая предсказуемость и упрощая управление состоянием.
- Что такое агрегация?
Это отношение между объектами, где один объект содержит другой, но оба могут существовать независимо друг от друга.
- Разница между наследованием и композицией? (abstract class vs interface)
Наследование создает иерархию классов с повторным использованием кода через базовый класс, тогда как композиция строит объекты из других объектов, делегируя выполнение задач, что обеспечивает большую гибкость и меньшую связанность.
- Как понять что код качественно написан?
• Читаемость - код легко понять другим разработчикам; используются понятные имена переменных и функций.
• Структурированность - код организован логически, следует принципам SOLID, DRY и KISS.
• Тестируемость - написаны юнит-тесты, обеспечивающие покрытие ключевых функций.
• Отсутствие дублирования - повторяющиеся блоки кода вынесены в отдельные функции или классы.
• Эффективность - код оптимален по производительности и использованию ресурсов.
• Документация - достаточное количество комментариев и документации, описывающей функциональность.
- Какие еще бывают принципы проектирования кроме SOLID?
• DRY
• KISS
• YAGNI
- Что такое Trunk Based Development?
Метод разработки программного обеспечения, при котором все разработчики работают в одной основной ветке кода, часто называемой «trunk» или «main». Основная идея TBD заключается в том, чтобы минимизировать количество веток и слияний, что способствует более быстрому и стабильному процессу разработки.
- Какие преимущества у многомодульности в Android?
• Ускорение сборки: разделение кода на модули позволяет пересобирать только изменённые модули, что сокращает время сборки проекта.
• Улучшение структуры кода: модули помогают разделить код по функциям или слоям (например, app, data, domain), повышая читаемость и поддерживаемость.
• Повторное использование и тестирование: отдельные модули можно повторно использовать в других проектах или легко тестировать изолированно.
• Снижение зависимостей: модули помогают контролировать зависимости между компонентами, обеспечивая лучшую изоляцию и уменьшая связанные ошибки.
- Какие недостатки у многомодульности в Android?
• Сложность настройки и поддержания: управление зависимостями (циклисчность) и архитектурой становится сложнее с увеличением количества модулей, требуя дополнительных усилий для организации и поддержания структуры проекта.
• Увеличение времени сборки проекта целиком: хотя многомодульность ускоряет сборку отдельных модулей, полная сборка всех модулей может занять больше времени из-за пересборки зависимостей.
• Усложнение навигации в коде: с увеличением числа модулей становится сложнее ориентироваться в кодовой базе, что может замедлить разработку для новых участников команды.
• Повышенные накладные расходы на кросс-модульное тестирование: тестирование функционала, затрагивающего несколько модулей, становится сложнее и требует дополнительных настроек.
- Что нравится в Android-разработке?