Очередная статья про «азы программирования на 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++ — это обязательно ООП. А все остальное — якобы нет.
ООП — это не возможности языка. НЕТ! Это архитектура приложения, способ моделирования предметной области. При котором используется разделение на сущности по принципу, описанному в статье.
Возможности языка могут облегчать применение принципов ООП, могут не мешать, а могут усложнять применение этих принципов. Но принципы от этого не меняются.
Я могу на C++ сделать «плохой» класс. Который нарушает принцип согласованного изменения состояния.
А могу на 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 или на Питоне реализовать «правильный» ООП. Или даже на ассемблере. Я выделю сущности и не позволю изменять их состояние извне.
Основная мысль моей статьи не о возможностях компиляторов.
А о ситуации, когда программист, использующий ООП, размышляет — «нужен тут класс (объектов) или не нужен».
Если есть состояние с поведением — нужен, иначе — не нужен.
Как он будет реализовывать это на своем инструменте программирования — мне не важно.