ООП: принципы объектно-ориентированного программирования

Объектно-ориентированное программирование, или ООП, — это способ организовать код вокруг объектов, которые объединяют данные и действия над ними. На нем построены Python, Java, C#, C++, Kotlin, Swift и почти все крупные приложения, от мобильных игр до банковских систем. Четыре принципа ООП — инкапсуляция, наследование, полиморфизм и абстракция — спрашивают на любом собеседовании для начинающего разработчика. Ниже объяснение каждого принципа с примерами кода на Python, структура ООП, сильные и слабые стороны подхода и языки, в которых он применяется.
Курсы программирования с нуля:
Python-разработчик — Яндекс Практикум, 150 000 ₽
Основы Python — Академия Эдюсон, 75 000 ₽
Разработка на Python — Бруноям, 79 800 ₽
Продвинутая разработка на C# и .NET — Яндекс Практикум, 130 500 ₽
Все курсы программирования — программы в каталоге РБК
Что такое ООП и зачем оно нужно
До появления ООП программы писали в процедурном стиле: данные лежали отдельно, функции отдельно, и любая функция могла изменить любые данные. Пока программа умещалась в несколько сотен строк, это работало. На больших проектах появлялись проблемы: изменение одной структуры данных ломало десятки функций в разных местах, а понять, какая функция за что отвечает, становилось все труднее.
ООП предложило другую организацию. Данные и функции, которые с ними работают, объединяются в одну сущность, объект. У объекта есть состояние, то есть его данные, и поведение, то есть методы. Объект «заказ» хранит список товаров и сумму и умеет добавлять товар и считать скидку. Снаружи с заказом общаются только через его методы, а как он устроен внутри, знать не нужно.
Такой подход отражает то, как люди думают о мире: мы оперируем предметами с их свойствами и возможностями, а не отдельными числами и процедурами. Первым языком с классами и объектами была Simula, созданная в Норвегии в 1960-х, а сам термин «объектно-ориентированное программирование» ввел Алан Кэй, автор языка Smalltalk.
В русскоязычном поиске аббревиатура ООП чаще означает «основная образовательная программа» школы. Эта статья про программирование.
Структура ООП: классы, объекты, атрибуты и методы
Класс — это описание, шаблон, по которому создаются объекты. Он задает, какие данные будут у каждого объекта и какие действия над ними возможны.
Объект — конкретный экземпляр класса, созданный по этому шаблону, со своими значениями данных.
Удобная аналогия: класс — чертеж автомобиля, объекты — автомобили, сошедшие с конвейера. Чертеж один, машин много, и у каждой свой цвет и свой пробег.
Атрибуты, или поля, — данные объекта. У автомобиля это марка, цвет, пробег.
Методы — функции, определенные внутри класса и работающие с его атрибутами. У автомобиля это «завести», «разогнаться», «показать пробег».
На Python класс и объекты выглядят так:
class Car:
def __init__(self, brand, color):
self.brand = brand
self.color = color
self.mileage = 0
def drive(self, km):
self.mileage += km
my_car = Car("Lada", "красный")
my_car.drive(120)
Метод __init__ называется конструктором: он вызывается при создании объекта и заполняет атрибуты. Параметр self — ссылка на сам объект, через нее методы обращаются к его данным. В Java и C# ту же роль играет this.
Четыре принципа ООП
Принципов ООП традиционно называют четыре. В некоторых источниках абстракцию не выделяют и говорят о трех китах, но на собеседованиях ожидают все четыре.
Инкапсуляция
Инкапсуляция — это объединение данных и методов внутри объекта и ограничение прямого доступа к внутреннему состоянию снаружи.
Смысл в том, что объект сам контролирует, как меняются его данные. Если у банковского счета баланс можно изменить напрямую, любой фрагмент программы может случайно записать туда отрицательное число. Если баланс закрыт, а изменить его можно только методами «пополнить» и «снять», проверки будут выполняться всегда.
В Python приватность обозначается соглашением: атрибут с одним подчеркиванием в начале имени считается внутренним, с двумя подчеркиваниями язык дополнительно переименовывает, чтобы затруднить случайный доступ. В Java и C# для этого есть модификаторы доступа private, protected и public.
class Account:
def __init__(self):
self.__balance = 0
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def balance(self):
return self.__balance
Снаружи нельзя написать account.__balance = −1000, только account.deposit(500). Доступ к данным идет через методы, которые называют геттерами и сеттерами.
Наследование
Наследование — это механизм, при котором новый класс создается на основе существующего и получает его атрибуты и методы.
Исходный класс называют родительским, базовым или суперклассом, новый — дочерним или подклассом. Дочерний класс может добавлять свои методы и переопределять унаследованные.
Наследование убирает дублирование. Если в программе есть классы «Кошка», «Собака» и «Хомяк», и у всех есть имя, возраст и метод «есть», эти общие части выносят в класс «Животное», а конкретные виды наследуют его и добавляют свое: кошка мяукает, собака лает.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Гав"
class Cat(Animal):
def speak(self):
return "Мяу"
Наследование описывает отношение «является»: собака является животным. Если отношение другое, «состоит из» или «использует», наследование применять не стоит: автомобиль не наследует от двигателя, а содержит его как атрибут. Такое отношение называют композицией, и в современных рекомендациях ее часто предпочитают глубоким иерархиям наследования.
Некоторые языки, C++ и Python, допускают множественное наследование, когда у класса несколько родителей. Java и C# разрешают наследовать только от одного класса, но позволяют реализовать несколько интерфейсов.
Полиморфизм
Полиморфизм — это возможность работать с объектами разных классов через одинаковый интерфейс, при этом каждый объект выполняет действие по-своему.
В примере выше у Dog и Cat есть метод speak с одним именем, но разным поведением. Благодаря этому можно написать один цикл для списка животных и не проверять, кто перед нами:
for animal in [Dog("Рекс"), Cat("Мурка")]:
print(animal.speak())
Программа выведет «Гав» и «Мяу». Код, который вызывает speak, ничего не знает о конкретных классах, и когда появится класс Cow, его не придется менять.
Полиморфизм бывает нескольких видов. Переопределение методов в подклассах, как в примере, — самый распространенный. Перегрузка, когда один метод существует в нескольких вариантах с разными параметрами, есть в Java, C# и C++. Утиная типизация в Python — крайний случай полиморфизма: если объект умеет speak, его можно использовать, независимо от того, от кого он унаследован.
Абстракция
Абстракция — это выделение существенных характеристик объекта и скрытие несущественных деталей.
Когда программист проектирует класс «Пользователь» для интернет-магазина, он оставляет имя, почту и адрес доставки и не включает рост, цвет глаз и группу крови, хотя у реального человека они есть. Класс описывает не человека целиком, а ту его сторону, которая важна для задачи.
На уровне кода абстракция выражается через абстрактные классы и интерфейсы. Абстрактный класс объявляет методы без реализации и требует, чтобы подклассы их реализовали. Так фиксируется контракт: любая «Фигура» обязана уметь вычислять площадь, а как именно, решает «Круг» или «Квадрат».
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
Создать объект Shape напрямую нельзя, только через конкретный подкласс. Абстракция и инкапсуляция близки, и их путают: инкапсуляция скрывает реализацию, абстракция решает, что вообще входит в модель.
Как принципы работают вместе
В реальном коде четыре принципа не существуют по отдельности. Абстракция определяет, какие классы нужны и за что отвечает каждый. Инкапсуляция защищает их внутреннее состояние. Наследование убирает повторение между родственными классами. Полиморфизм позволяет остальной программе работать с ними единообразно.
Пример из практики: платежный модуль интернет-магазина. Абстрактный класс PaymentMethod объявляет метод pay. Классы CardPayment, SbpPayment и WalletPayment наследуют его и реализуют оплату по-своему, скрывая детали вроде токенов и запросов к банку. Код оформления заказа вызывает method.pay(amount) и не знает, какой именно способ выбрал покупатель. Чтобы добавить новый способ оплаты, достаточно написать еще один класс, не трогая остальное.
Преимущества и недостатки ООП
Сильные стороны:
Модульность. Программа разбита на независимые части, каждую можно разрабатывать и тестировать отдельно.
Повторное использование. Классы переносятся между проектами, библиотеки строятся на общих базовых классах.
Поддерживаемость. Изменения локализованы: если поменялась логика скидок, правится класс скидок, а не десятки функций.
Близость к предметной области. Классы «Заказ», «Клиент», «Товар» понятны и аналитику, и заказчику.
Слабые стороны:
Порог входа. Нужно не только выучить синтаксис, но и научиться проектировать: решать, какие классы нужны и как они связаны. Плохо спроектированная иерархия хуже процедурного кода.
Накладные расходы. Создание объектов и вызовы методов требуют памяти и времени. В высоконагруженных вычислениях и на микроконтроллерах это заметно.
Избыточность на малых задачах. Для скрипта в двадцать строк классы не нужны.
Глубокие иерархии. При увлечении наследованием возникают цепочки из пяти-шести уровней, в которых трудно понять, откуда пришел метод.
Из-за этих ограничений ООП редко применяют в чистом виде. Python, JavaScript, Kotlin и Scala сочетают его с функциональным стилем, а Go и Rust отказались от классического наследования в пользу композиции и интерфейсов.
Языки объектно-ориентированного программирования
| Язык | Особенности ООП | Где применяется |
|---|---|---|
| Python | Все является объектом, множественное наследование, утиная типизация, приватность по соглашению | Веб, анализ данных, автоматизация, машинное обучение |
| Java | Строгая типизация, одиночное наследование, интерфейсы, все в классах | Корпоративные системы, Android, бэкенд |
| C# | Близок к Java, свойства, события, интерфейсы | Windows-приложения, игры на Unity, бэкенд на .NET |
| C++ | Множественное наследование, ручное управление памятью, шаблоны | Игры, драйверы, высокопроизводительные системы |
| Kotlin | Классы данных, расширения, совместимость с Java | Android |
| Swift | Классы и структуры, протоколы | iOS и macOS |
| JavaScript | Прототипное наследование, синтаксис классов с ES6 | Веб-интерфейсы, Node.js |
| PHP | Классы, интерфейсы, трейты | Веб-разработка |
Для первого знакомства с ООП обычно рекомендуют Python: синтаксис короткий, принципы видны без лишних конструкций, а официальный учебник по классам в документации языка занимает одну страницу. Java и C# строже и заставляют сразу думать о типах и модификаторах доступа, зато именно они чаще встречаются на собеседованиях с вопросами по ООП; в документации Oracle есть отдельный раздел с объяснением объектов, классов и наследования.
Что спрашивают про ООП на собеседовании
Вопросы по ООП входят в стандартный набор для стажеров и младших разработчиков независимо от языка. Чаще всего встречаются такие.
Чем класс отличается от объекта? Класс — описание, объект — экземпляр, созданный по этому описанию. У класса нет значений атрибутов, у объекта есть.
Назовите принципы ООП и объясните каждый одним предложением. Инкапсуляция скрывает внутреннее состояние, наследование передает свойства от родителя к потомку, полиморфизм позволяет работать с разными объектами одинаково, абстракция оставляет в модели только существенное.
В чем разница между абстрактным классом и интерфейсом? Абстрактный класс может содержать реализованные методы и состояние, а наследовать можно только один такой класс. Интерфейс описывает лишь набор методов, и реализовать можно несколько интерфейсов сразу. В Python роль интерфейсов играют абстрактные классы и протоколы.
Что такое переопределение и перегрузка? Переопределение — замена реализации унаследованного метода в подклассе. Перегрузка — несколько методов с одним именем и разными параметрами в одном классе; в Python ее в классическом виде нет.
Когда наследование вредно? Когда оно используется ради повторного использования кода, а не ради отношения «является». Тогда изменения в родителе ломают потомков, а иерархия растет вглубь. Альтернатива — композиция.
Что такое конструктор и деструктор? Конструктор создает объект и задает начальное состояние, деструктор вызывается при уничтожении объекта и освобождает ресурсы. В языках со сборкой мусора деструкторы используют редко.
Что такое SOLID? Пять принципов проектирования классов: единственная ответственность, открытость для расширения и закрытость для изменения, подстановка Лисков, разделение интерфейсов, инверсия зависимостей. Их спрашивают реже четырех принципов ООП, но ответ на уровне расшифровки аббревиатуры ожидается.
Хороший ответ на любой из этих вопросов содержит пример из кода или из собственного проекта. Определение, выученное наизусть без примера, интервьюеры обычно просят пояснить.
Как учить ООП
Четыре принципа запоминаются за вечер, а умение проектировать классы приходит только с практикой. Рабочая последовательность такая:
1. Написать один класс с конструктором, атрибутами и двумя-тремя методами. Подойдет любой бытовой объект: книга, счет, задача из списка дел.
2. Добавить второй класс, который использует первый: библиотека хранит книги, список дел хранит задачи. Здесь появляется композиция.
3. Построить небольшую иерархию с наследованием и переопределением: фигуры, животные, сотрудники разных должностей.
4. Переписать иерархию через абстрактный класс или интерфейс и убедиться, что внешний код не изменился.
5. Взять существующий учебный проект, написанный процедурно, и переложить его на классы. На этом шаге становится понятно, зачем ООП нужно.
Дальше изучают паттерны проектирования и принципы SOLID, которые описывают, как строить классы, чтобы код оставался гибким. Этими темами обычно завершается модуль по ООП в программах курсов Python-разработки и Java-разработки. О том, какие направления в программировании востребованы и сколько зарабатывают разработчики, РБК писал в обзоре профессии программиста, а инструменты, которые ускоряют написание кода, собраны в подборке AI-инструментов для разработчиков.