Получить доступ

ООП и его принципы

Инкапсуляция, наследование, полиморфизм, абстракция — зачем они нужны, как работают внутри и почему композицию предпочитают наследованию.

Зачем нужно ООП

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

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

Главная цель — управлять сложностью: прятать детали за понятным интерфейсом и собирать большое из небольших заменяемых частей. Четыре принципа ниже — это инструменты для этой цели, а не пункты для заучивания.

Инкапсуляция: класс охраняет свои инварианты

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

Суть не в том, чтобы «сделать поля private и приклеить геттеры/сеттеры». Суть — в инвариантах: правилах, которые всегда должны быть истинны (баланс не отрицателен, список отсортирован). Методы — это место, где правила проверяются.

Поэтому бездумные сеттеры на каждое поле ломают инкапсуляцию не хуже public-полей: они снова позволяют менять состояние в обход проверок. Хорошая инкапсуляция выставляет наружу осмысленные операции (deposit), а не присваивания (setBalance).

class Account {
    private long balance; // состояние под защитой

    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
        balance += amount; // инвариант «balance >= 0» под контролем
    }

    public long balance() { return balance; } // только чтение, без сеттера
}

Абстракция: что показать, а что спрятать

Абстракция — выделение существенного: какие операции тип предоставляет наружу, без подробностей, как они устроены внутри. В Java её выражают интерфейсы и абстрактные классы — это контракт, на который опираются клиенты.

Частая путаница: абстракция отвечает на вопрос «что делает тип» (контракт), а инкапсуляция — «как он защищает своё состояние» (сокрытие). Они дополняют друг друга, но это не одно и то же.

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

Наследование: расширение через «is-a»

Наследование (extends) задаёт отношение «является»: подтип — это разновидность базового типа. Он получает поля и методы родителя, а часть поведения переопределяет через @Override; к версии родителя обращаются через super.

Наследование переиспользует код, но его цена — жёсткая связь. Проблема «хрупкого базового класса» (fragile base class): изменение базового класса может незаметно сломать наследников, потому что они зависят от его внутреннего поведения, а не только от контракта.

Поэтому наследоваться стоит, лишь когда отношение действительно «is-a» и контракт базового типа стабилен.

Полиморфизм: один интерфейс — много реализаций

Полиморфизм позволяет работать с разными реализациями через общий тип, а конкретный метод выбирается во время выполнения по фактическому классу объекта. Это динамическая диспетчеризация (late binding).

Не путайте два механизма: переопределение (overriding) — это и есть рантайм-полиморфизм; перегрузка (overloading) — выбор метода по типам аргументов на этапе компиляции и к полиморфизму подтипов отношения не имеет.

Полиморфизм — двигатель расширяемости: добавляя новую реализацию интерфейса, вы не трогаете код, который с ним работает. Это и есть принцип open/closed — открыт для расширения, закрыт для изменения.

interface Shape { double area(); }

class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    public double area() { return Math.PI * r * r; }
}

class Square implements Shape {
    private final double a;
    Square(double a) { this.a = a; }
    public double area() { return a * a; }
}

// area() выбирает реализацию по фактическому типу объекта:
double total = 0;
for (Shape s : shapes) total += s.area();

Композиция вместо наследования

Композиция — отношение «имеет» (has-a): объект делегирует работу другим объектам, которые держит внутри, вместо того чтобы наследоваться от них.

Композиция гибче: зависимость можно подменить (в том числе в рантайме и в тестах), нет хрупкой связи с базовым классом и нет риска унаследовать лишнее. Отсюда правило «composition over inheritance».

Классический пример: Stack не должен наследовать ArrayList (иначе наружу протекут add/remove по индексу, ломающие LIFO). Лучше держать список внутри и выставить только push/pop.

// плохо: class Stack<T> extends ArrayList<T> — наследуем лишнее API
class Stack<T> {
    private final List<T> items = new ArrayList<>(); // has-a
    public void push(T x) { items.add(x); }
    public T pop() { return items.remove(items.size() - 1); }
}

Частые заблуждения

Считать, что private-поля с геттерами/сеттерами — это и есть инкапсуляция. Инкапсуляция про инварианты, а не про шаблонные аксессоры.

Сводить полиморфизм к перегрузке методов вместо переопределения.

Путать абстракцию (контракт типа) и инкапсуляцию (сокрытие состояния).

Наследоваться ради переиспользования кода там, где отношения «is-a» на самом деле нет.

Как отвечать на собеседовании

Назовите четыре принципа и дайте по одному короткому примеру: инкапсуляция (поле под проверяющим методом), наследование (extends + @Override), полиморфизм (вызов переопределённого метода по ссылке базового типа), абстракция (интерфейс как контракт).

Сильный ответ добавляет связку: инкапсуляция бережёт инварианты, абстракция задаёт контракт, полиморфизм даёт расширяемость (open/closed), а наследование стоит предпочитать композиции. Это показывает, что принципы для вас — инструменты, а не заученный список.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем