В очередной раз прочитал в качестве определения «объектно-ориентированности» что-то типа «это структурки и код для работы с ними«. Удивительно, как популярная (но не единственная) реализация так повлияла на понимание термина. Речь идет прежде всего о том, как «объектно-ориентированность» реализована в C++.
Для начала — попытаемся понять, что же такое «объектно-ориентированность» в программировании. Мне очень нравится определение из SICP:
Существует мощная стратегия разработки, которая особенно хорошо подходит для построения программ, моделирующих физические системы: воспроизводить в структуре программы структуру моделируемой системы. Для каждого объекта в системе мы строим соответствующий ему вычислительный объект. Для каждого действия в системе определяем в рамках нашей вычислительной модели символьную операцию.
…
Таким образом, способ, которым мы организуем большую программу, в значительной степени диктуется нашим восприятием моделируемой системы. В этой главе (3 глава SICP) мы исследуем две важных организационных структуры, которые соответствуют двум достаточно различным взглядам на мир и структуру систем. Первая из них сосредотачивается на объектах (objects) и большая система рассматривается как собрание индивидуальных объектов, поведение которых может меняться со временем.
Объектно-ориентированное программирование (а точнее, проектирование) в классическом его понимании — это представление «предметной области» в виде множества объектов, каждый из которых обладает своим внутренним состоянием. В том же классическом представлении предполагается, что объекты могут обмениваться сообщениями — и изменяют свое состояние только реагируя на эти сообщения.
Эта модель в явной форме была реализована в языках типа Simula или Smalltalk. Из более современного можно назвать, например, VRML — это не просто «формат файлов» типа Autocad или 3D Studio, а практически полноценная реализация вышеназванного принципа. Из «настоящих» языков программирования — можно привести в качестве примера Objective C. Очень похожи на модель с обменом сообщениями «сигналы» и «слоты» библиотеки Qt — но в данном случае речь идет о диком смешении двух подходов к ООП, и вряд ли это стоит считать подходящим примером.
Па-а-азвольте, скажут тут знатоки современных языков программирования — где же тут private и public переменные, где здесь методы? И вот тут придется вспомнить про то, что C++ — первый широко используемый «объектно-ориентированный» язык, но создавался он исключительно как «C с классами», причем эти «классы», как и все объектно-ориентированные возможности раннего C++, были лишь имитацией таких возможностей в «настоящих» объектно-ориентированных языках.
Важно понимать, что «вызов метода» в C++ — это лишь реализация «передачи сообщения» от одного объекта другому. Фактически, в C++ вместо передачи сообщений используются три разных «примитива». Например, возьмем вот такой класс:
class A { public: int x; int f(int y); private: int z; };
… и вот такой код, работающий с ним:
A a; int b; a.x = 2; b = a.x; b = a.f(3);
В С++ эти три строчки — принципиально разные операции. На самом деле все они могут быть рассмотрены, как частный случай «посылки сообщения» (а ее мы будем записывать в виде адресат.сообщение(аргументы)
):
class A { public: int get_x(); void set_x(int new_x); int f(int y); private: int x, z; }; ... A a; int b; a.set_x(2); b = a.get_x(); b = a.f(3);
Замечу, что «накладные расходы» второго способа оказываются несколько больше. Но «идеологически» и присваивание, и чтение значения, и вызов метода — это одна и та же операция. Все переменные-члены класса перемещаются в секцию private — и состояние объекта действительно становится «внутренним».
В «настоящих» объектно-ориентированных языках, типа Smalltalk, с помощью модели передачи сообщений реализовывались даже такие вещи, как индексация массивов или циклы. В принципе, несложно подобным образом реализовать и арифметические вычисления — с помощью каких-нибудь специально созданных объектов, типа «сумма» (я тут перейду на «русский» псевдокод):
объект Формула { методы: Число вычислить() - чисто виртуальный; } объект Сумма : Формула { методы: Число вычислить(); задатьПервоеСлагаемое(); задатьВтороеСлагаемое(); переменные состояния: Формула первоеСлагаемое, второеСлагаемое; } объект Константа : Формула { методы: Число вычислить(); задатьЗначение(Число); переменные состояния: Число значение; }
Думаю, что реализации расписывать не надо? А вот как это можно использовать:
Константа к1, к2, к3; Сумма с1, с2; Число результат; к1.задатьЗначение(1); к2.задатьЗначение(2); к3.задатьЗначение(3); с1.задатьПервоеСлагаемое(к1); с1.задатьВтороеСлагаемое(к2); с2.задатьПервоеСлагаемое(с1); с2.задатьВтороеСлагаемое(к3); результат = с2.вычислить();
Естественно, что вышеописанное — это некоторое извращение, и все соответствующие объекты должны создаваться сразу, как только мы напишем результат = (1+2)+3;
. «Метод» в нашей терминологии — это не более чем описание «формата» получаемого сообщения и реакции на него.
С++ — это попытка «приделать» объекты к существующему «необъектному» языку C. В C уже были и структурки, и функции — именно поэтому в C++ «классы» — это некоторое обобщение структур C. После того, как C++ стал действительно распространен — аналогичный подход «взяли на вооружение» разработчики других языков. Именно поэтому в «современном» ООП практически нельзя встретить «чистую» передачу сообщений — только подход «объект — это данные плюс функции».
Жутким монстром на фоне этого всего выглядит Qt — где умудрились объединить и обмен сообщениями (в виде сигналов и слотов), и «объекты в стиле C++». Получилось что-то в духе чудовища Франкенштейна — хотя иногда и довольно удобное в использовании.
Не вижу принципиальной разницы между сообщениями и функциями.
Кроме того, не очень ясно, ты осуждаешь подход с++? Я не уловил основную мысль
Разница есть. Человечество за свою историю изобрело некоторое количество «вычислительных моделей». Всякие там машины Тьюринга и фон Неймана — это далеко не все. Программисты пользуются куда большим количеством разных моделей. Тут и поддерживаемая ассемблером модель с ячейками памяти, операциями над ними и условными переходами, и модель «классических» языков программирования, со структурками, вызовом функций, операторами ветвления и цикла, и модель рекурсивных функций (Lisp и РЕФАЛ), и модель «объектов, обменивающихся сообщениями». «Вызов функции» — термин из одной модели, «передача сообщения» — из другой. Существование C++ и ООП-языков типа Java — это фактически доказательство эквивалентности двух моделей. Просто в разговоре «метод класса — это типа как функция» утверждается существование только одной из них, более знакомой, а вторая благополучно забывается.
Подход C++ я не осуждаю, он нормальный. Просто понимание того, что на самом деле есть «вызов метода», может оказаться очень полезным.
А вот скажи: вызов метода происходит в момент выхова метода или ставится в очередь на исполнение?
Где? В языках типа C++ или Java — никакой «очереди на исполнение» в явном виде нет. В UML, на «диаграмме кооперации» есть два типа стрелочек — асинхронная и синхронная передача сообщений. В «теории» — можно делать и так, и так.
теорию в виде сферического объектного коня в объектно-ориентированном ваккууме можно оставить в стороне для будущих поколений, как и стрелочки диаграмм.
а в реальности обращение к методу есть обращение к функции и что бы что-то «передать» таким образом, недостаточно иметь ссылку на объект, необходимо прилинковать и код метода.
Вот делаешь ту же ошибку, подменяешь представление единственно известной реализацией. Не говоря уже о том, что слово «прилинковать» относится уже к второму слою реализации.
Стрелочки диаграмм, что характерно, относятся к вполне работающим технологиям. UML — это не совсем «сферический конь в вакууме», а все-таки формальная нотация для уже существующих разработок.
И – боже вас сохрани – не читайте до обеда советских газет.
– Гм… Да ведь других нет.
– Вот никаких и не читайте.
Вот я и не использую ООП, поскольку других реализаций и нет.
ну и мне важны все слои реализации, а не только уровень маркетолухных зазываний.
Кстати, столкнулся недавно с Objective C, проблевался конкретно. :)
> мне важны все слои реализации
Как обстоят дела c пониманием работы современных микропроцессоров?
на уровне отдельного ключа слАбо.
на уровне «All write operations are read-modify-write operations. So a write to a port implies that the port pins are first read, then this value is modified and written to the port data latch» вполне приемлимо.
:)
Слушай, я наверное путаюсь в терминологии, но мне всегда казалось, что «сообщения» — это нечто асинхронное, в контрасте к «вызову метода» — синхронная операция. Нет?
Нет. Определение «сообщения» этого не подразумевает. В UML на диаграмме кооперации предусмотрено два типа изображающих сообщения стрелок — синхронные и асинхронные.
Ну да, значит в терминах запутался.