Очередная статья про «азы программирования на C++» меня подтолкнула к мысли, что многие программисты не понимают сути объектно-ориентированного программирования (ООП).



В частности, в этой статье утверждается, что
"С++ очень прост в том смысле, что классы С++ повторяют описание объектов реального мира. "

Довольно давно на одном из форумов я наткнулся на рассуждения еще одного программиста, который хотел запрограммировать систему хранения «универсальных» объектов и рассуждал о том, «является ли стул — объектом».

Оба эти человека так или иначе заблуждаются насчет ООП.

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

Путаница часто возникает еще и из-за затасканного слова «объект». Да еще и понятия «класс» и «объект» часто смешивают.

Итак, простое правило, которое позволит легко понять, где ООП, а где — нет.

Фары и ремень

ПОВЕДЕНИЕ И СОСТОЯНИЕ

Более развернуто, класс объектов (в понимании ООП) используют, если…

… у некоторой сущности есть поведение, зависящее от внутреннего состояния этой сущности.

Что значит «у сущности есть состояние»? Давайте разберемся.

У экземпляра сущности есть набор собственных данных — это очевидно. В чем отличие от обычной записи (структуры)?

Прежде всего, состояние подразумевает, что часть данных в сущности используются «для себя», для реализации собственного поведения. Если сущности «нечего скрывать», то она вырождается в обыкновенную запись данных. Более того, понятие «состояния» является более строгим, чем просто «дополнительный набор данных». Оно предполагает некоторую связанность, взаимозависимость этих внутренних данных. Состояние может быть корректным и некорректным. А кто может это понять? Внешний наблюдатель? Нет, только сама сущность. Таким образом, состояние скрыто не просто так, оно защищено от пов��еждения некомпетентным пользователем сущности.

Теперь уже проясняется, что такое «поведение» сущности. Это когда сущность реализует какой-то функционал, сохраняя корректность состояния.

Давайте какой-нибудь простой пример возьмем. Есть товар: наименование, цена за единицу, остаток на складе. Это объект ООП?

Подумайте прежде чем читать дальше.

Если не накладывать никаких ограничений на его характеристики — нет. Состояния не возникает.
Мы можем заносить любые значения. Скрывать нечего.

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

А теперь?

Может теперь внешний пользователь произвольно менять данные в товаре? Очевидно, нет. У нашего товара появилось поведение — уменьшение остатка означает «забрать товар в определенном количестве» и забрать больше чем остаток — нельзя.

Заметили, что произошло? Мы ввели ограничения, то есть внесли некий смысл в природу свойств сущности. И сразу же появилось осмысленное поведение — «внести товар», «забрать товар».

Другой пример. Предположим в программе требуется динамическая загрузка модулей. Есть состояние — набор уже загруженных модулей. И поведение легко просматривается — «загрузить модуль», «выгрузить модуль». К чему этот пример? К тому, что никакой связи с объектами в реальном мире здесь и в помине нет.

Связывать природу классов в ООП с природой объектов реального мира — довольно абсурдная и бессмысленная затея. Природа сущности будет определяться не реальным миром, а системой и зависеть от окружения сущности, от ее использования, от взаимосвязи компонент.

Вот и все. Можно называть это инкапсуляцией. А можно и не называть. Можно долго рассуждать на тему наследования. А можно просто понять, что «наследование» — это способ расширения поведения объекта.

Главное — понять, что ООП нужно программисту, чтобы контролировать сложность разработки. А не для того, чтобы «отражать объекты реального мира».

UPD

Ладно, продолжим кидаться умными словами.

Абстракция и полиморфизм не имеют прямого отношения к ООП. Как ни странно.

Любая процедура или функция являются абстракцией. Любая. Вот объявление:

void sort(int* array, int size);

Эта функция является абстракцией. Она реализует сортировку. Абстрактно. Мы не знаем как.
Но на выходе будет отсортированный массив. Абстракция есть, а где тут ООП? Нету ООП. Ни объектов, ни классов.

Теперь я объявил указатель sort. И могу ему присвоить такую функцию:

void quick_sort(int* array, int size)

или такую:

void bubble_sort(int* array, int size).

Возник полиморфизм. Указатель на функцию остался прежним. Но теперь за одним и тем же указателем на функцию стоят разные алгоритмы.

А где ООП? ООП нету. Потому что это даже не C++, это C. В котором нету классов. А абстракция и полиморфизм достигнуты.

Угу?

UPD2

Хм: Для ООП не обязательно иметь какие-то там классы ;)

Берем обычный C, делаем структуры с указателями на функции, договариваемся первым аргументом всегда передавать указатель на this и не обращаться к членам структуры, не являющимся указателями на функции, иначе, чем через этот this. Чем не ООП?

В python, например, нельзя спрятать данные (по крайней мере без особых ухищрений) - он не является ООП языком?


Вот именно. Я как раз об этом :-) Я же нигде вообще ни строчки ни написал о том, что C++ — это обязательно ООП. А все остальное — якобы нет.

ООП — это не возможности языка. НЕТ! Это архитектура приложения, способ моделирования предметной области. При котором используется разделение на сущности по принципу, описанному в статье.

Возможности языка могут облегчать применение принципов ООП, могут не мешать, а могут усложнять применение этих принципов. Но принципы от этого не меняются.

Я могу на C++ сделать «плохой» класс. Который нарушает принцип согласованного изменения состояния.

А могу на C или на Питоне реализовать «правильный» ООП. Или даже на ассемблере. Я выделю сущности и не позволю изменять их состояние извне.

Основная мысль моей статьи не о возможностях компиляторов.

А о ситуации, когда программист, использующий ООП, размышляет — «нужен тут класс (объектов) или не нужен».

Если есть состояние с поведением — нужен, иначе — не нужен.

Как он будет реализовывать это на своем инструменте программирования — мне не важно.