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