Перейти к содержанию

ООП

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

Четыре принципа ООП

Инкапсуляция

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

class Cart {
    private val items = mutableListOf<Item>()

    fun add(item: Item) {
        require(item.quantity > 0)
        items += item
    }

    fun snapshot(): List<Item> = items.toList()
}

Cart владеет изменяемой коллекцией, проверяет изменения и возвращает снимок только для чтения, не раскрывая сам список.

Абстракция

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

interface UserRepository {
    suspend fun user(id: UserId): User
}

ViewModel может зависеть от этого контракта, а не от Room или сетевого клиента.

Наследование

Наследование выражает отношение является и предполагает, что подтип может заменить базовый тип без неожиданностей для вызывающего кода. Классы и методы Kotlin по умолчанию финальны; используйте open только для намеренно созданной точки расширения. Предпочитайте неглубокие иерархии. Наследование от Android API, например ViewModel, не означает, что прикладное поведение тоже следует строить на глубоких базовых классах.

Полиморфизм

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

Предпочитайте композицию для переиспользования поведения

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

class RepositoryWrapper(
    private val delegate: UserRepository,
) : UserRepository by delegate

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

Изменяемость и владение состоянием

Неизменяемый объект нельзя изменить после создания. Ссылка только для чтения, например val или List<T>, не гарантирует глубокую неизменяемость: связанные объекты всё ещё могут меняться. Неизменяемые снимки упрощают анализ, тестирование и отображение состояния интерфейса в Compose. Локализуйте неизбежные изменения у явного владельца, а наружу предоставляйте состояние и операции или события, запрашивающие его изменение.

Связанные темы: SOLID, Паттерны проектирования и Основы архитектуры.