Основы ООП

Модель

Объектно-ориентированная программа строится из объектов с идентичностью, состоянием и поведением. Класс задаёт тип и правила допустимого состояния, а объект является конкретным экземпляром.

Сильная объектная модель не просто хранит поля: она не позволяет обойти инварианты.

final class Account {
    private BigDecimal balance;
 
    Account(BigDecimal initial) {
        if (initial.signum() < 0) {
            throw new IllegalArgumentException("negative balance");
        }
        balance = initial;
    }
 
    void withdraw(BigDecimal amount) {
        if (amount.signum() <= 0 || balance.compareTo(amount) < 0) {
            throw new IllegalArgumentException("invalid amount");
        }
        balance = balance.subtract(amount);
    }
}

Вместо публичного setBalance объект предоставляет операцию, которая сохраняет правило «баланс не отрицателен».

Четыре базовых механизма

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

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

Абстракция

Тип показывает существенные свойства и скрывает лишние детали. List выражает упорядоченную последовательность, не заставляя пользователя знать устройство ArrayList.

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

Подтип наследует доступное поведение базового типа и должен сохранять его контракт. Наследование выражает отношение «является» и создаёт сильную связь. См. Наследование.

Полиморфизм

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

interface PricePolicy {
    BigDecimal apply(BigDecimal base);
}
 
BigDecimal finalPrice(PricePolicy policy, BigDecimal base) {
    return policy.apply(base);
}

Подробно: Полиморфизм.

Композиция

Композиция выражает отношение «содержит/использует»:

final class CheckoutService {
    private final PricePolicy pricePolicy;
 
    CheckoutService(PricePolicy pricePolicy) {
        this.pricePolicy = pricePolicy;
    }
}

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

Entity и value object

  • Entity важна своей стабильной идентичностью и меняющимся состоянием.
  • Value object определяется значением, обычно неизменяем и сравнивается по полям.
record Money(BigDecimal amount, Currency currency) {}

Не всякий DTO должен становиться «богатым» объектом; модель должна соответствовать задаче и границам системы.

Ловушки

  • Сводить инкапсуляцию к генерации getters/setters.
  • Использовать наследование только ради повторного использования кода.
  • Создавать «god object», который знает и делает всё.
  • Выносить каждую строку в отдельный интерфейс без реальной вариативности.
  • Путать объектную модель с точной копией предметов реального мира.
  • Нарушать контракт базового типа в наследнике.

Вопросы на собеседовании

  1. Что такое идентичность, состояние и поведение объекта?
  2. Чем инкапсуляция отличается от одного лишь private?
  3. Что даёт динамический полиморфизм?
  4. Когда композиция предпочтительнее наследования?
  5. Чем entity отличается от value object?
  6. Почему публичные setters могут разрушать инварианты?

Связанные заметки

Объекты Java · Класс Object · Инкапсуляция · Наследование · Полиморфизм · Абстрактные классы · Интерфейсы Java