Введение в ООП
Процедурное программирование
Знакомство с объектно-ориентированным программированием (ООП) трудно представить без понимания основ процедурного подхода. Фактически, ООП возникло как ответ на ограничения, присущие процедурному программированию.
Процедурное программирование по сути является базовым подходом. В его рамках большая задача разбивается на малые пошаговые инструкции (операторы, функции), которые затем последовательно выполняются компьютером. Важно отметить, что в этой парадигме данные (например, состояние ингредиентов) и функции, которые их обрабатывают, существуют отдельно друг от друга.
Представим, что мы хотим описать процесс приготовления пиццы с помощью кода в процедурном стиле. Это выглядело бы примерно так:
Приготовление пиццы в процедурном стиле
НАЧАЛО
НАРЕЗКА ИНГРЕДИЕНТОВ;
ЗАМЕШИВАНИЕ ТЕСТА;
ДОБАВЛЕНИЕ НАЧИНКИ;
ЗАПЕКАНИЕ ПИЦЦЫ;
НАРЕЗКА ПИЦЦЫ;
КОНЕЦ.
При этом каждая из приведённых команд должна быть отдельно и подробно реализована. Так, алгоритм замешивания теста, в свою очередь, включал бы смешивание муки с водой, раскатку теста и т.д. Функции принимали бы данные, обрабатывали их и передавали результат дальше по цепочке.
Если добавить к примеру реализации всех функций, включая вспомогательные (которые не вызываются напрямую), код станет очень громоздким и неинтуитивным. Станет непонятно, какие функции предназначены для вызова извне, а какие являются внутренними. Эта проблема особенно остро проявлялась в реальной разработке, когда количество строк в файлах исчислялось тысячами. Такой код прозвали спагетти-кодом, так как он представлял собой большой, запутанный клубок инструкций с неочевидными связями.
У объёмного процедурного кода есть и другой недостаток — сложность внесения изменений. Часто системы, написанные в процедурном стиле, были очень хрупкими, поскольку изменение алгоритма работы одной функции приводило к некорректной работе остальных. Происходило это из-за того, что функции часто зависели от общих данных, и изменение состояния в одном месте непредсказуемо влияло на другие. Представьте, что в нашем примере потребовалось поддержать ещё и безглютеновое тесто. В процедурном стиле пришлось бы вносить правки в каждую функцию, где используется тесто.
Объектно-ориентированное программирование
Появление концепции объектно-ориентированного программирования ознаменовало новый скачок в развитии программирования.
Согласно ней, программа состоит из объектов, взаимодействующих между собой. Каждый объект обладает характерными свойствами (поля) и умеет выполнять определённые действия (методы). Список всех свойств и возможных действий перечисляется в определении класса. Такой подход позволяет легко переводить объекты из реального мира на язык программирования.
К примеру, опишем упрощённый процесс запуска автомобиля в объектно-ориентированной парадигме.
Для начала выделим основные составляющие этого процесса. Самым очевидным элементом является “Автомобиль”. Однако в ООП мы не просто пишем функцию ЗАПУСК_АВТОМОБИЛЯ, а представляем систему как набор взаимодействующих объектов. Когда водитель проворачивает ключ зажигания, объект “Автомобиль” не выполняет всю логику сам, а делегирует задачу: он подаёт сигнал объекту “Стартер”, который, в свою очередь, запускает объект “Двигатель” (Рисунок).

Рисунок – Диаграмма классов
Запуск автомобиля с точки зрения внешнего пользователя будет представлять из себя вызов Автомобиль.Включить. При этом внутри будет произведена цепочка вызовов Стартер.Крутить -> Двигатель.Запустить. Если представлять данную программу в виде псевдо-кода, то получится:
Объектно-ориентированный псевдо-код
Класс Двигатель:
поле количество_лошадинных_сил
поле пробег
поле состояние = "выключен"
метод запустить():
если состояние == "выключен":
проверить свечи
подать топливо
состояние = "работает"
Класс Стартер:
поле напряжение
поле мощность
метод крутить(двигатель):
двигатель.запустить()
Класс Автомобиль:
поле модель_автомобиля
поле цвет
поле двигатель = новый Двигатель()
поле стартер = новый Стартер()
метод включить():
стартер.крутить(двигатель)
Точка входа программы:
объект lada_granta = новый Автомобиль()
lada_granta.модель_автомобиля = "Lada Granta"
lada_granta.цвет = "Белый"
lada_granta.включить()
Строки “новый Двигатель()”, “новый Стартер()” и “новый Автомобиль()” означают создание объектов соответствующих классов.
Стоит различать:
Класс - описание объектов, которое содержит свойства и действия.
Объект - конкретный экземпляр класса.
Например, "Автомобиль" является классом (абстракцией), а модели "Lada Granta", "Mitsubishi Lancer", "Toyota Mark 2" - объектами.
Такой подход имеет несколько преимуществ.
Во-первых, разделяется ответственность. Каждый объект сам отвечает за свою работу. Объект “Двигатель” знает, как именно себя запустить (проверить свечи, подать топливо, искру). Нам не нужно выносить эти детали в общие функции. Если мы захотим изменить конструкцию двигателя, код запуска автомобиля переписывать не придётся - достаточно обновить только метод внутри объекта “Двигатель”.
Во-вторых, ясность интерфейса. Внешнему коду не нужно знать, сколько именно компонентов участвует в запуске. Чтобы завести машину, достаточно вызвать один метод Автомобиль.Включить. Все внутренние взаимодействия (стартер, двигатель, бензонасос, электроника) скрыты внутри объекта.
В-третьих, улучшение понимания кода. Сущности реального мира явным образом переносятся в термины “Класс” и “Объект”, за счёт чего код гораздо лучше воспринимается, чем набор из множества функций в одном файле.