Irbisa · Шпаргалка

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

ООП и SOLID в Kotlin, DRY, KISS и YAGNI, связность и зацепление, внедрение зависимостей, паттерны GoF и где их используют Android и его библиотеки, антипаттерны

Middle-разработчик · 19 вопросов

  1. 01

    Что означают DRY, KISS и YAGNI, и когда следование DRY делает код хуже?

    DRY требует, чтобы у каждого знания было одно место, KISS предлагает выбирать самое простое работающее решение, а YAGNI запрещает строить под требования, которых ещё нет.

    // DRY: one rule, one home; the UI hint is derived from it, not copiedobject PasswordRule {    const val MIN_LENGTH = 8    fun isValid(password: String) = password.length >= MIN_LENGTH    val hint = "At least $MIN_LENGTH characters"}…
  2. 02

    Что такое связанность (coupling) и связность (cohesion), и почему стремятся к низкой связанности и высокой связности?

    Связанность (coupling) показывает, насколько один класс зависит от того, как устроен другой; связность (cohesion) показывает, насколько части одного класса относятся к одному делу.

  3. 03

    Что такое паттерн Observer, и в каких формах он встречается на Android?

    Observer позволяет объекту (субъекту) оповещать любое число зависимых об изменениях, не зная, кто они.

    fun interface CartListener {    fun onCartChanged(count: Int)} class Cart {    private val listeners = mutableListOf<CartListener>()…
  4. 04

    Что такое паттерн Strategy, и как его записать на Kotlin?

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

    // a stateless one-method strategy: a function type is enoughtypealias Discount = (subtotalCents: Long) -> Long val noDiscount: Discount = { it }val blackFriday: Discount = { it * 70 / 100 }fun coupon(offCents: Long): Discount = { it - minOf(offCents, it / 5) }   // at most 20% off…
  5. 05

    Что выведет этот код, и какой принцип SOLID нарушает Square?

    open class Rectangle {    open var width = 0    open var height = 0    val area get() = width * height} class Square : Rectangle() {    override var width: Int        get() = super.width        set(value) { super.width = value; super.height = value }    override var height: Int        get() = super.height        set(value) { super.width = value; super.height = value }} fun resize(r: Rectangle) { r.width = 5; r.height = 4; println(r.area) }fun main() { resize(Rectangle()); resize(Square()) }

    Код выведет 20, а затем 16.

  6. 06

    Как принцип разделения интерфейсов (Interface Segregation) применяется к листенерам и репозиториям на Android, и спасают ли толстый интерфейс методы по умолчанию?

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

    // fat: a screen that only shows the profile also depends on uploads and account deletioninterface UserRepository {    suspend fun profile(id: Long): Profile    suspend fun updateName(id: Long, name: String)    suspend fun uploadAvatar(id: Long, jpeg: ByteArray)    suspend fun deleteAccount(id: Long)…
  7. 07

    Нужен ли каждому классу интерфейс? Когда оправдан интерфейс с единственной реализацией?

    Нет. Интерфейс оправдан там, где реализацию нужно подменять: фейк в тестах, другая платформа, граница модуля, которая не должна видеть настоящий класс.

  8. 08

    Чем фабричная функция отличается от Factory Method и Abstract Factory, и где Android использует каждую?

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

    // factory function: named like the type, validates and hides the implementation classinterface Session { val userId: Long }private class ActiveSession(override val userId: Long) : Session fun Session(userId: Long): Session {    require(userId > 0) { "invalid user id: $userId" }…
  9. 09

    Именованные аргументы и значения по умолчанию заменяют в Kotlin большинство Builder'ов. Когда Builder всё ещё уместен, и почему он остался в OkHttp и Retrofit?

    Builder по-прежнему окупается, когда объект настраивают по шагам в разных местах, когда build() должен проверить опции вместе, когда из готового объекта выводят варианты и когда API библиотеки должен оставаться бинарно совместимым; OkHttp и Retrofit попадают во все четыре случая.

    class HttpConfig private constructor(    val baseUrl: String,    val timeoutMs: Long,    val headers: Map<String, String>,) {    fun newBuilder() = Builder(this)                        // derive a variant…
  10. 10

    Найдите баг: запросы картинок к стороннему CDN уносят access token пользователя.

    val base = OkHttpClient.Builder()    .connectTimeout(10, TimeUnit.SECONDS)    .readTimeout(20, TimeUnit.SECONDS) val apiClient = base    .addInterceptor(AuthInterceptor(tokens))    .build() // meant to be: the same timeouts, without authval cdnClient = base.build()

    OkHttpClient.Builder изменяемый: addInterceptor добавляет интерсептор в сам base и возвращает тот же билдер, поэтому последующий base.build() создаёт второй клиент, в котором тоже есть AuthInterceptor, и каждый запрос картинки отправляет Authorization на CDN.

  11. 11

    Adapter, Decorator, Proxy и Facade все оборачивают другой объект. Как их различить, и является ли RecyclerView.Adapter настоящим Adapter?

    По намерению: Adapter меняет интерфейс, чтобы несовместимый код подошёл друг к другу, Decorator сохраняет интерфейс и добавляет поведение, Proxy сохраняет интерфейс и управляет доступом к настоящему объекту, а Facade ставит один более простой интерфейс перед целой подсистемой.

    interface ImageSource {    suspend fun load(url: String): ByteArray} // Adapter: a callback SDK made to fit the interface the app expectsclass SdkImageSource(private val sdk: LegacySdk) : ImageSource {…
  12. 12

    Что такое Flyweight и Object Pool, и зачем существуют Drawable.mutate() и Message.obtain()?

    Flyweight разделяет одно тяжёлое состояние между многими лёгкими объектами; Object Pool переиспользует изменяемые объекты вместо создания новых.

    // Flyweight: drawables from one resource share a ConstantState until mutate()val star = ContextCompat.getDrawable(context, R.drawable.ic_star)!!val dimmed = ContextCompat.getDrawable(context, R.drawable.ic_star)!! dimmed.alpha = 80             // wrong: star and every other ic_star fade toodimmed.mutate().alpha = 80    // right: dimmed gets its own copy of the state first…
  13. 13

    Как работает Chain of Responsibility, и как построить её на Kotlin, например для маршрутизации диплинков?

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

    sealed interface Route {    data class Product(val id: String) : Route    data class Order(val id: Long) : Route    data object Home : Route}…
  14. 14

    Что такое паттерн Template Method, почему Activity обязана вызывать super.onCreate(), и что заменило переопределение в новых API Android?

    Template Method фиксирует шаги алгоритма в базовом классе и даёт подклассам заполнить хуки.

    sealed interface SyncResult {    data object Skipped : SyncResult    data class Done(val count: Int) : SyncResult} // the template: the order of steps is fixed in the base class…
  15. 15

    Что такое паттерн Command, и как с его помощью сделать undo в редакторе?

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

    interface EditCommand {    fun execute(doc: StringBuilder)    fun undo(doc: StringBuilder)} class Insert(private val at: Int, private val text: String) : EditCommand {…
  16. 16

    Как написать конечный автомат на Kotlin с sealed-классами, и чем паттерн State отличается от Strategy?

    Сделайте каждое состояние sealed-подтипом, который несёт только свои данные, и соберите все переходы в одну функцию (State, Event) -> State, которую компилятор проверяет на полноту.

    sealed interface Upload {    data object Idle : Upload    data class Sending(val file: String, val attempt: Int) : Upload    data class Done(val url: String) : Upload    data class Failed(val file: String, val reason: String) : Upload}…
  17. 17

    Open/Closed в Kotlin: когда sealed-иерархия с when лучше полиморфного интерфейса, и почему Kotlin редко нужен Visitor?

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

    // open to new TYPES: another module adds a provider without touching this codeinterface PaymentProvider {    val id: String    fun feeCents(amountCents: Long): Long}…
  18. 18

    Каждый экран приложения наследует BaseActivity глубиной в пять уровней. Что идёт не так, и как перевести это на композицию?

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

    // before: every screen inherits these checks, in super-call orderopen class BaseActivity : AppCompatActivity() {    override fun onResume() {        super.onResume()        if (!session.isLoggedIn) startActivity(Intent(this, LoginActivity::class.java))        analytics.screen(javaClass.simpleName)…
  19. 19

    Какие антипаттерны вы ищете, когда получаете чужую Android-кодовую базу, и в каком порядке их рефакторите?

    Обычные подозреваемые: god Activity, свалки Utils и Manager, глобальные изменяемые синглтоны и скопированная логика.