Инкапсуляция, наследование, полиморфизм: три кита ООП
Объектно-ориентированное программирование держится на трёх принципах: инкапсуляции, наследовании и полиморфизме (часто к ним добавляют абстракцию). Эти три кита постоянно путают на собеседованиях и экзаменах, потому что каждый по отдельности звучит просто, а вместе они отвечают за разные вещи: инкапсуляция прячет внутренности объекта, наследование переиспользует код, полиморфизм даёт один интерфейс для разных типов. Ниже разберём, что делает каждый принцип, покажем их на коротких примерах кода и увидим, сколько строк они экономят. Чтобы сразу почувствовать эффект, покрутите калькулятор ниже: он превращает дизайн классов в считаемую экономию кода и долю скрытых полей.
Инкапсуляция: спрятать состояние за интерфейсом
Инкапсуляция - это объединение данных и методов в один объект с сокрытием внутреннего состояния. Поля делают приватными (private), а доступ к ним открывают только через методы: геттеры, сеттеры или операции с проверкой. Смысл не в секретности ради секретности, а в защите инварианта: внешний код не может присвоить балансу счёта отрицательное значение или сломать внутренний массив, минуя проверки.
Классический пример - банковский счёт. Если поле balance публичное, любой кусок программы может записать туда что угодно. Сделав его приватным и оставив только методы deposit и withdraw с проверками, мы гарантируем, что баланс всегда остаётся согласованным.
Доля скрытых полей - это и есть мера инкапсуляции. На шкале в калькуляторе она считается как отношение приватных полей к общему числу: . Чем ближе к 100%, тем меньше внешнего кода способно нарушить состояние объекта. В дизайне по умолчанию шесть приватных полей из восьми дают долю 75% - типичный здоровый уровень.
Наследование: переиспользовать код, а не копировать
Наследование позволяет создать новый класс на основе существующего, унаследовав его поля и методы. Подкласс получает всё поведение базового класса и добавляет своё. Это прямой способ не дублировать код: общую логику пишут один раз в базовом классе, а специфику - в наследниках.
Возьмём геометрические фигуры. Базовый класс Figure хранит цвет и умеет выводить описание, а классы Circle, Rectangle, Triangle наследуют это и добавляют только расчёт своей площади. Без наследования цвет и описание пришлось бы копировать в каждый класс.

Экономия наследования прямая и считаемая: если базовый класс с методами наследуют подклассов, то без наследования эти методы пришлось бы переписать в каждом, то есть продублировать раз. При пяти методах базового класса и пяти подклассах это уже 20 лишних методов, которых наследование избегает. Глубина наследования в калькуляторе показывает, как число доступных методов накапливается вниз по цепочке: база отдаёт свои методы дочернему классу, тот свои - внуку, и так далее.
Важно не злоупотреблять: глубокие цепочки наследования (когда уровней пять и больше) делают код хрупким, потому что изменение в базе задевает всех потомков. Композицию (объект включает другой объект как поле) часто предпочитают наследованию именно по этой причине.
Полиморфизм: один вызов для разных типов
Полиморфизм - это способность объектов разных классов отвечать на один и тот же вызов по-своему. Если у базового класса есть метод area, а каждый подкласс его переопределяет, то вызов figure.area() сам выберет нужную реализацию в зависимости от фактического типа объекта. Программист пишет один обобщённый код, работающий со всеми типами сразу.
Главный выигрыш полиморфизма виден при сравнении со switch. Без полиморфизма обработка разных типов превращается в ветвление: в каждой точке вызова нужен switch по типу объекта с веткой на каждый класс. С полиморфизмом точка вызова одна, а выбор реализации берёт на себя механизм виртуальных методов.

Считаемая экономия: при точках полиморфного вызова и типах switch потребовал бы ветвей, а полиморфизм оставляет вызовов. Убрано ветвей. И главное - при добавлении нового типа полиморфный код не меняется вообще, а switch-версию пришлось бы править в каждой точке вызова. Это и есть принцип открытости-закрытости в действии.
Как три принципа работают вместе
На практике принципы не существуют поодиночке. Инкапсуляция прячет состояние, наследование задаёт иерархию типов, полиморфизм заставляет эту иерархию работать через единый интерфейс. В примере с фигурами: каждое поле приватно (инкапсуляция), все фигуры наследуют базовый Figure (наследование), а цикл по списку фигур вызывает area() не зная конкретных типов (полиморфизм).
Калькулятор выше показывает суммарный эффект: для дизайна по умолчанию код с ООП занимает 31 строку-эквивалент против 75 при подходе с копипастой и switch - почти на 59% короче. При этом инкапсуляция в этой картине стоит особняком: она не про объём кода, а про надёжность, поэтому её меряют отдельной шкалой доли скрытых полей.
Абстракция и где она в этом ряду
Часто спрашивают про четвёртый принцип - абстракцию. Абстракция - это выделение существенных характеристик объекта и сокрытие несущественных деталей; абстрактный класс или интерфейс задаёт контракт без реализации. Многие считают её отдельным китом, многие - частным случаем инкапсуляции. Для экзамена достаточно помнить, что абстракция отвечает на вопрос «что объект умеет», а инкапсуляция - «как это устроено внутри и почему скрыто».
Частые ошибки
- Путают инкапсуляцию и абстракцию. Инкапсуляция прячет данные и связывает их с методами, абстракция выделяет существенное и задаёт контракт. Это разные идеи, хотя обе про сокрытие.
- Считают приватные поля «секретностью». Смысл private не в защите от хакера, а в защите инварианта: чтобы состояние объекта нельзя было привести в недопустимое значение в обход проверок.
- Путают перегрузку и переопределение. Полиморфизм времени выполнения - это переопределение (override) метода в подклассе. Перегрузка (overload) методов с разными аргументами - это другой механизм, разрешаемый на этапе компиляции.
- Злоупотребляют наследованием. Глубокие иерархии делают код хрупким. Если связь не «является разновидностью», а «содержит», нужна композиция, а не наследование.
- Думают, что наследование и полиморфизм - одно и то же. Наследование переиспользует код, полиморфизм даёт единый интерфейс. Полиморфизм опирается на наследование или интерфейсы, но решает другую задачу.
FAQ
Чем инкапсуляция отличается от наследования и полиморфизма? Инкапсуляция отвечает за сокрытие данных внутри объекта и доступ к ним только через методы. Наследование переиспользует код базового класса в подклассах. Полиморфизм даёт один интерфейс для объектов разных типов. Это три независимые задачи, которые ООП решает совместно.
Является ли абстракция четвёртым принципом ООП? В разных источниках по-разному: где-то называют три принципа (инкапсуляция, наследование, полиморфизм), где-то четыре, добавляя абстракцию. Абстракция тесно связана с инкапсуляцией, поэтому её часто рассматривают как её часть, а не отдельный принцип.
Как полиморфизм связан с наследованием? Полиморфизм времени выполнения обычно реализуется через наследование: подкласс переопределяет метод базового класса, и вызов через ссылку на базовый тип выбирает реализацию подкласса. Но полиморфизм возможен и через интерфейсы без наследования реализации.
Коротко
Три кита ООП решают разные задачи: инкапсуляция прячет состояние за методами и защищает инвариант, наследование переиспользует код базового класса в подклассах, полиморфизм даёт один вызов для разных типов и убирает ветвление switch. Инкапсуляцию меряют долей скрытых полей , наследование экономит продублированных методов, полиморфизм убирает веток. Вместе они делают код короче и устойчивее к изменениям.
Читайте также

Полиморфизм в ООП: пример на простом коде и разбор
Полиморфизм в ООП на примере: один вызов метода работает по-разному для разных классов. Разбираем перегрузку, переопределение, виртуальные методы и утиную типизацию на коде.

Кодоминирование: наследование групп крови системы ABO
Кодоминирование в генетике на примере групп крови ABO: аллели I^A и I^B одновременно экспрессируются, образуя IV группу. Разбор генотипов, задачи, таблица фенотипов.

Летальные гены: наследование и расщепление 2:1
Как наследуются летальные гены: почему расщепление в потомстве дает 2:1 вместо 1:2:1, доминантные и рецессивные летали, время действия и разбор задач по генетике.