Введение в ООП

Процедурное программирование

Знакомство с объектно-ориентированным программированием (ООП) трудно представить без понимания основ процедурного подхода. Фактически, ООП возникло как ответ на ограничения, присущие процедурному программированию.

Процедурное программирование по сути является базовым подходом. В его рамках большая задача разбивается на малые пошаговые инструкции (операторы, функции), которые затем последовательно выполняются компьютером. Важно отметить, что в этой парадигме данные (например, состояние ингредиентов) и функции, которые их обрабатывают, существуют отдельно друг от друга.

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

Приготовление пиццы в процедурном стиле

НАЧАЛО
    НАРЕЗКА ИНГРЕДИЕНТОВ;
    ЗАМЕШИВАНИЕ ТЕСТА;
    ДОБАВЛЕНИЕ НАЧИНКИ;
    ЗАПЕКАНИЕ ПИЦЦЫ;
    НАРЕЗКА ПИЦЦЫ;
КОНЕЦ.

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

Если добавить к примеру реализации всех функций, включая вспомогательные (которые не вызываются напрямую), код станет очень громоздким и неинтуитивным. Станет непонятно, какие функции предназначены для вызова извне, а какие являются внутренними. Эта проблема особенно остро проявлялась в реальной разработке, когда количество строк в файлах исчислялось тысячами. Такой код прозвали спагетти-кодом, так как он представлял собой большой, запутанный клубок инструкций с неочевидными связями.

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

Объектно-ориентированное программирование

Появление концепции объектно-ориентированного программирования ознаменовало новый скачок в развитии программирования.

Согласно ней, программа состоит из объектов, взаимодействующих между собой. Каждый объект обладает характерными свойствами (поля) и умеет выполнять определённые действия (методы). Список всех свойств и возможных действий перечисляется в определении класса. Такой подход позволяет легко переводить объекты из реального мира на язык программирования.

К примеру, опишем упрощённый процесс запуска автомобиля в объектно-ориентированной парадигме.

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

Диаграмма классов

Рисунок – Диаграмма классов

Запуск автомобиля с точки зрения внешнего пользователя будет представлять из себя вызов Автомобиль.Включить. При этом внутри будет произведена цепочка вызовов Стартер.Крутить -> Двигатель.Запустить. Если представлять данную программу в виде псевдо-кода, то получится:

Объектно-ориентированный псевдо-код

Класс Двигатель:
    поле количество_лошадинных_сил
    поле пробег
    поле состояние = "выключен"
    метод запустить():
        если состояние == "выключен":
            проверить свечи
            подать топливо
            состояние = "работает"

Класс Стартер:
    поле напряжение
    поле мощность
    метод крутить(двигатель):
        двигатель.запустить()

Класс Автомобиль:
    поле модель_автомобиля
    поле цвет
    поле двигатель = новый Двигатель()
    поле стартер = новый Стартер()
    метод включить():
        стартер.крутить(двигатель)

Точка входа программы:
    объект lada_granta = новый Автомобиль()
    
    lada_granta.модель_автомобиля = "Lada Granta"
    lada_granta.цвет = "Белый"

    lada_granta.включить()

Строки “новый Двигатель()”, “новый Стартер()” и “новый Автомобиль()” означают создание объектов соответствующих классов.

Стоит различать:

Класс - описание объектов, которое содержит свойства и действия.

Объект - конкретный экземпляр класса.

Например, "Автомобиль" является классом (абстракцией), а модели "Lada Granta", "Mitsubishi Lancer", "Toyota Mark 2" - объектами.

Такой подход имеет несколько преимуществ.

Во-первых, разделяется ответственность. Каждый объект сам отвечает за свою работу. Объект “Двигатель” знает, как именно себя запустить (проверить свечи, подать топливо, искру). Нам не нужно выносить эти детали в общие функции. Если мы захотим изменить конструкцию двигателя, код запуска автомобиля переписывать не придётся - достаточно обновить только метод внутри объекта “Двигатель”.

Во-вторых, ясность интерфейса. Внешнему коду не нужно знать, сколько именно компонентов участвует в запуске. Чтобы завести машину, достаточно вызвать один метод Автомобиль.Включить. Все внутренние взаимодействия (стартер, двигатель, бензонасос, электроника) скрыты внутри объекта.

В-третьих, улучшение понимания кода. Сущности реального мира явным образом переносятся в термины “Класс” и “Объект”, за счёт чего код гораздо лучше воспринимается, чем набор из множества функций в одном файле.

← Назад к оглавлению

Следующая тема: Введение в ООП. Инкапсуляция →

Следующая тема: Введение в ООП. Инкапсуляция →