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

Принципов ООП четыре: инкапсуляция, наследование, полиморфизм и абстракция. Разбираем каждый на одном примере интернет-магазина с кодом на Python и настоящим выводом, показываем, когда наследование вредит и чем абстракция отличается от инкапсуляции. В конце схема ответа на собеседовании.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 2906 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3560 экспертных мнений Подписаться на Телеграм-канал
Обложка: Принципы ООП: инкапсуляция, наследование, полиморфизм и абстракция на примерах

Принципов ООП четыре: инкапсуляция, наследование, полиморфизм и абстракция. Инкапсуляция прячет данные объекта и пускает к ним только через методы. Наследование даёт новому классу код родителя. Полиморфизм позволяет одним вызовом работать с объектами разных классов, а абстракция оставляет в классе только то, что нужно для задачи.

Ниже каждый принцип разобран на одном сквозном примере, интернет-магазине с заказами, уведомлениями и оплатой. Код на Python, мы запустили его на версиях 3.12 и 3.14, вывод в статье настоящий. Если слова «класс», «объект» и «метод» пока путаются, начните со статьи «ООП простыми словами», а запустить примеры поможет инструкция как создать файл Python в VS Code.

CheckroiCheckroiПодборка курсов по ООП171 курс • 33 школыСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Четыре принципа ООП в одной таблице

Рой раскладывает четыре принципа ООП на столе

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

Принцип Какую проблему решает Как выглядит в Python
Инкапсуляция Данные объекта портят из любого места программы Атрибут с подчёркиванием _total, доступ через методы и @property
Наследование Один и тот же код копируют в несколько классов class Email(Notification) и вызов super()
Полиморфизм Код обвешан проверками «если это письмо, то…, если СМС, то…» У разных классов метод с одним именем, цикл зовёт send()
Абстракция Детали реализации расползаются по всему проекту Абстрактный класс из модуля abc задаёт обязательные методы

Принципы работают вместе. В примерах ниже вы увидите, как абстракция задаёт контракт, наследование его передаёт, полиморфизм им пользуется, а инкапсуляция защищает данные внутри каждого объекта.

Инкапсуляция: объект сам отвечает за свои данные

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

Возьмём заказ. Его статус должен меняться строго по порядку: новый, оплачен, отправлен, доставлен. Если любой код может написать order.status = "доставлен", рано или поздно кто-то пропустит оплату.

class Order:
    STATUSES = ("новый", "оплачен", "отправлен", "доставлен")

    def __init__(self, number, total):
        self.number = number
        self._total = total
        self._status = "новый"

    @property
    def status(self):
        return self._status

    def advance(self):
        i = self.STATUSES.index(self._status)
        if i == len(self.STATUSES) - 1:
            raise ValueError("заказ уже доставлен")
        self._status = self.STATUSES[i + 1]


order = Order(1024, 3490)
order.advance()
print(order.status)
order.status = "доставлен"
оплачен
AttributeError: property 'status' of 'Order' object has no setter

Статус можно прочитать, но записать напрямую нельзя: свойство status объявлено только на чтение. Двигать заказ дальше умеет один метод advance(), и правило порядка живёт ровно в одном месте.

Как скрывают данные в Python и Java

В Python нет настоящих приватных полей. Одно подчёркивание (_total) это договорённость между программистами: «не трогай снаружи». Два подчёркивания включают name mangling, интерпретатор переименовывает атрибут, но достать его всё равно можно. Механизм описан в официальном учебнике Python.

class Card:
    def __init__(self, pin):
        self.__pin = pin


card = Card("1234")
print(card._Card__pin)
print(card.__pin)
1234
AttributeError: 'Card' object has no attribute '__pin'

В Java доступ проверяет компилятор. У полей и методов четыре уровня: private (только свой класс), без модификатора (свой пакет), protected (пакет и наследники) и public. Таблица уровней есть в руководстве Oracle. Модификаторы доступа входят в типовые вопросы технического интервью, о пути в профессию подробнее в статье как стать Java-разработчиком.

CheckroiCheckroiПодборка курсов по Java118 курсов • 22 школыСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→ Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Наследование: общий код живёт в родителе

С наследованием в ООП новый класс создают на основе существующего. Дочерний класс получает атрибуты и методы родителя и добавляет своё.

В магазине есть уведомления: письма и СМС. У обоих есть получатель, текст и короткое превью для админки. Этот общий код пишем один раз в классе Notification.

class Notification:
    def __init__(self, recipient, text):
        self.recipient = recipient
        self.text = text

    def preview(self):
        return f"{self.recipient}: {self.text[:20]}"

    def send(self):
        raise NotImplementedError


class Email(Notification):
    def __init__(self, recipient, text, subject):
        super().__init__(recipient, text)
        self.subject = subject

    def send(self):
        return f"письмо «{self.subject}» → {self.recipient}"


class Sms(Notification):
    def send(self):
        return f"СМС {len(self.text)} симв. → {self.recipient}"


mail = Email("anna@example.com", "Ваш заказ 1024 отправлен", "Заказ в пути")
print(mail.preview())
print(Email.__mro__)
anna@example.com: Ваш заказ 1024 отпра
(<class '__main__.Email'>, <class '__main__.Notification'>, <class 'object'>)

Метода preview() в классе Email нет, Python нашёл его у родителя. Порядок поиска показывает атрибут __mro__: сначала свой класс, потом родитель, в конце базовый object.

Ещё одно различие языков:

  • Python разрешает наследоваться сразу от нескольких классов, порядок поиска методов задаёт тот же MRO;
  • Java позволяет расширить только один класс, зато реализовать сколько угодно интерфейсов. Причину Oracle объясняет в разделе о множественном наследовании: так язык избегает конфликтов, когда два родителя по-разному задают одно поле.

Когда наследование вредит

Наследник получает от родителя всё, включая то, что ему не нужно. Классический случай: стек, унаследованный от списка.

class Stack(list):
    def push(self, item):
        self.append(item)


s = Stack()
s.push("a")
s.push("b")
s.insert(0, "в обход стека")
print(s)
['в обход стека', 'a', 'b']

Стек должен класть и забирать элементы только сверху, а через унаследованный insert() элемент попал в самый низ. Решение: композиция. Класс хранит список внутри и открывает наружу только нужные методы.

class SafeStack:
    def __init__(self):
        self._items = []

    def push(self, item):
        self._items.append(item)

    def pop(self):
        return self._items.pop()


safe = SafeStack()
safe.push("a")
safe.insert(0, "в обход стека")
AttributeError: 'SafeStack' object has no attribute 'insert'

Проверка перед наследованием: можно ли сказать «Email является уведомлением»? Да, значит наследование уместно. «Стек является списком»? Нет, стек хранит элементы в списке, это композиция.

Тот же вопрос лежит в основе принципа подстановки Лисков из набора SOLID. Его и соседние правила мы разобрали в статье о принципах KISS, DRY и SOLID.

Полиморфизм: один вызов, разное поведение

Одна кнопка запускает разные устройства

Полиморфизм в ООП означает, что код работает с объектами разных классов через общий метод и не знает, какой именно объект перед ним. Слово переводится как «много форм»: вызов один, а поведение у каждого класса своё.

Очередь уведомлений в магазине не должна разбираться, письмо перед ней или СМС. Ей достаточно, чтобы у каждого элемента был метод send(). Классы Email и Sms берём из примера выше и добавляем к ним бота.

class TelegramBot:
    def __init__(self, chat_id):
        self.chat_id = chat_id

    def send(self):
        return f"сообщение в чат {self.chat_id}"


queue = [
    Email("anna@example.com", "Заказ отправлен", "Заказ в пути"),
    Sms("+7 900 000-00-00", "Заказ отправлен"),
    TelegramBot(51843),
]
for item in queue:
    print(item.send())
письмо «Заказ в пути» → anna@example.com
СМС 15 симв. → +7 900 000-00-00
сообщение в чат 51843

Цикл из трёх строк обслуживает все каналы. Появится push-уведомление, и в цикле ничего менять не придётся: новый класс с методом send() просто встанет в очередь.

Утиная типизация

Посмотрите на TelegramBot. Он не наследует Notification, но цикл с ним работает: Python не проверяет тип, он вызывает метод. Такой стиль называют утиной типизацией, определение есть в глоссарии Python. Если нужна проверка ещё до запуска, общий метод описывают протоколом из модуля typing, правила изложены в PEP 544.

Переопределение и перегрузка

Полиморфизм встречается в двух видах, и на собеседованиях их часто просят различить.

Вид Что происходит Python Java
Переопределение (override) Наследник заменяет метод родителя своей версией Есть: send() в Email и Sms Есть, с аннотацией @Override
Перегрузка (overload) В одном классе несколько методов с одним именем и разными параметрами Нет: второй метод с тем же именем затирает первый Есть, компилятор выбирает метод по списку параметров

Перегрузку в Java описывает раздел руководства Oracle об объявлении методов. В Python похожего поведения добиваются аргументами по умолчанию. Если вы ещё выбираете язык под Android или бэкенд, сравнение двух соседей по JVM есть в статье Java или Kotlin.

Абстракция: главное снаружи, детали внутри

Абстракция в ООП выделяет в объекте то, что важно для задачи, и задаёт это как контракт. Остальное прячется в конкретных классах. Магазину важно, что платёж можно провести, а как именно банк списывает деньги, решает реализация.

В Python контракт описывают абстрактным классом из модуля abc. Создать объект такого класса нельзя, а наследник обязан реализовать все абстрактные методы.

from abc import ABC, abstractmethod


class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount):
        ...

    def pay(self, amount):
        if amount <= 0:
            raise ValueError("сумма должна быть больше нуля")
        return self.charge(amount)


class CardGateway(PaymentGateway):
    def charge(self, amount):
        return f"списали {amount} ₽ с карты"


class SbpGateway(PaymentGateway):
    pass


print(CardGateway().pay(3490))
SbpGateway()
списали 3490 ₽ с карты
TypeError: Can't instantiate abstract class SbpGateway without an implementation for abstract method 'charge'

Класс SbpGateway забыли дописать, и Python остановил программу ещё при создании объекта, до того как покупатель нажал «Оплатить». Проверка суммы при этом написана один раз в pay() и работает для любого способа оплаты.

Платёжные шлюзы, очереди уведомлений и статусы заказов составляют обычную работу серверного программиста. Как прийти в эту специальность, разобрано в статье как стать backend-разработчиком.

Абстракция и инкапсуляция: в чём разница

Эти два принципа путают чаще остальных, потому что оба что-то «скрывают».

  • Абстракция решает, что объект показывает миру: метод pay() есть у любого платёжного шлюза.
  • Инкапсуляция решает, как объект защищает своё состояние: статус заказа нельзя переписать в обход advance().

Первая работает на уровне проектирования классов, вторая на уровне конкретного объекта и его полей.

Почему абстракцию иногда не считают принципом

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

Для практики разница небольшая. На собеседовании надёжнее назвать четыре и коротко пояснить, почему абстракцию иногда не считают отдельным принципом.

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

CheckroiCheckroiПодборка курсов по Python463 курса • 48 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Частые ошибки новичков

  1. Геттер и сеттер на каждое поле. Если сеттер просто записывает любое значение, данные защищены не больше, чем открытый атрибут. Решение: оставлять методы, которые проверяют правило, как advance() у заказа.
  2. Глубокие цепочки наследования. Пять уровней родителей приходится помнить, чтобы понять один метод. Решение: два-три уровня, остальное через композицию.
  3. Наследование ради пары общих строк. Классы без отношения «является» склеиваются, и правка родителя ломает чужой код. Решение: вынести общий код в функцию или отдельный класс-помощник.
  4. Цепочки isinstance вместо полиморфизма. Каждый новый тип уведомления заставляет дописывать ещё одну ветку if. Решение: общий метод у всех классов, как send() выше.
  5. Абстрактный класс с одной реализацией. Если способ оплаты один и второго не планируется, контракт только добавляет файлов. Решение: вводить абстракцию, когда появилась вторая реализация.
Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Как ответить про принципы ООП на собеседовании

Рой уверенно отвечает на собеседовании

Вопрос «назовите принципы ООП» входит в типовой набор для джунов, на каком бы языке они ни писали. Хороший ответ укладывается в минуту и строится по одной схеме.

  1. Назвать четыре принципа и дать каждому определение в одну фразу.
  2. Показать пример из своего кода или из привычной предметной области: заказ, уведомления, платежи.
  3. Добавить нюанс: в Python приватность основана на соглашении, а в Java на модификаторах; наследование не всегда лучше композиции.

Нюанс отличает человека, который писал код, от того, кто выучил определения. Как подготовиться к остальным этапам, рассказано в статье как пройти собеседование разработчику, а общий план входа в профессию в материале как стать программистом с нуля.

Где освоить ООП системно

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

Такую практику дают курсы, где объектно-ориентированное программирование разбирают на проектах. Ниже подборка из нашего каталога курсов по ООП, а если вы выбираете язык, посмотрите и курсы по Python.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Профессия «Python-разработчик»
Перейти на сайт курса
SkillboxSkillbox157 335 ₽5987 ₽/мес.10 месяцевОбзор курса
PHP-разработчик с нуля до PRO
Перейти на сайт курса
SkillboxSkillbox114 876 ₽4176 ₽/мес.7 месяцевОбзор курса
Frontend-разработчик с нуля
Перейти на сайт курса
НетологияНетология113 400 ₽5385 ₽/мес.10 месяцевОбзор курса
Fullstack-разработчик на Python
Перейти на сайт курса
НетологияНетология161 200 ₽7125 ₽/мес.21 месяцОбзор курса
PHP-разработчик. Базовый уровень
Перейти на сайт курса
SkillboxSkillbox81 107 ₽5227 ₽/мес.3 месяцаОбзор курса

Больше программ — в полном каталоге курсов по оОП

Часто задаваемые вопросы

Сколько принципов у ООП?

Обычно называют четыре: инкапсуляцию, наследование, полиморфизм и абстракцию. В части учебников принципов три, абстракцию туда не включают, потому что ею пользуются и в коде без объектов.

Чем инкапсуляция отличается от абстракции?

Абстракция решает, что объект показывает наружу: например, метод pay() есть у любого платёжного шлюза. Инкапсуляция защищает внутреннее состояние объекта, чтобы его нельзя было изменить в обход этих методов.

Есть ли в Python приватные атрибуты?

Строгих нет. Одно подчёркивание в имени атрибута означает договорённость «не трогать снаружи», а два включают name mangling: атрибут __pin переименовывается в _Card__pin и остаётся доступен. Механизм описан в учебнике Python.

Можно ли наследоваться от нескольких классов?

В Python можно, порядок поиска методов показывает атрибут __mro__. В Java класс расширяет только один класс, зато может реализовать сколько угодно интерфейсов.

Принципы ООП и SOLID это одно и то же?

Это разные наборы. Четыре принципа описывают, из чего состоит объектно-ориентированное программирование, а SOLID состоит из пяти правил проектирования классов поверх ООП. Их мы разобрали в статье о принципах KISS, DRY и SOLID.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!