Once upon a time thou didst write unto All:
AU> вернуться к двум крестам Си. Hо поступим иначе. У нас была выделена
AU> специальная команда, которая должна была разработать механизм объектных
AU> сообщений. Дадим им слово. Когда мы им сказали какого типа объекты будут
AU> использоваться в нашей системе, они разработали иерахию сообщений. Да,
AU> каждое сообщение является классом,
Гм-гм, вот с этим переходом у меня возникла заминка. Hу с данными в таком
классе вроде все ясно, но ведь смысл-то не в них, а в обработке...
AU> но удивительно не только это, а и то, что сообщения обрабатываемые
AU> каждым классом компилируются вместе с кодом данного класса. Это в первом
AU> приближении можно представить, как таблицу виртуальных методов, только
AU> раздроблённую на кусочки.
Гм-гм, не смог я себе это что-то представить...
AU> Таким образом, каждое сообщение несёт в себе адрес функции его
AU> обрабатывающей.
Вот! Hо ведь обработка одного и того же сообщения для разных классов может
разнится, так откуда же берется эта ссылка?
AU> Когда контейнер получает такое сообщение, он подставляет в него ссылку на
AU> принадлежащий экземпляр объекта данного класса и производит вызов. И
AU> всё...
То есть контейнер определяет, какому именно объекту передать сообщение, но
обработкой будет занят некий метод самого сообщения, получающий этот объект как
бы в качестве аргумента? Это значит, что для каждого объекта должен создаваться
свои "подклассы" сообщений, им обрабатываемых, для описания присущего именно
этому объекту способу обработки сообщения (кажется, напоминает объекты (в смысле
классы :) *Processor в Object Professional)? (Гм, тут еще динамического
связывания методов с объектом не хватает, как, если мне не изменяет мой верный
склероз, Вирт наворотил в Модуле-3. :)
Правильно ли я понимаю или совсем нет? :)
Fare thee well o Alex!
... Happy hacking!
Ранее были рассмотрены примеры, показывающие возможность использования ОО
технологии при программировании на ассемблере. Следует отметить, в том что было
показано нет ничего нового для тех, кто уже писал на C++ и/или OO Pascal. Hо эти
языки являются процедурными, что, увы, наложило свой отпечаток. Сейчас мне бы
хотелось рассмотреть два примера демонстрирующие несколько иной взгляд на
применение технологии ООП. Первый пример иллюстрирует процесс разработки
графического интерфейса, второй описывает идеологию построения систем хранения
информации.
"Ошибка" фирмы Microsoft
Когда Microsoft приступила к созданию графической части OS/2, большинство
элементов графического интерфейса уже существовали в других системах и на других
платформах. Взяв готовые идеи, Microsoft не позаботилась о том, чтобы
реализовать их с учётом новых технологий, в том числе и ООП.
Hу, что ж никто не мешает нам провести свой эксперимент. Hо вначале надо ввести
всего одно понятие: контейнер-менеджер. Первоначально будем пользоваться
термином - контейнер, но, постепенно насыщая его логикой, доведём его
функциональные возможности до менеджера (это нечто иное, чем контейнер в C и
брокер объектных запросов в CORBA).
Дело в том, что постоянно возникает проблема увеличения функциональных
возможностей одного объекта за счёт функциональных возможностей другого.
Hапример, (красивый, но не мой пример) у нас есть класс - товар и класс -
автомобиль, а нам необходимо рассмотреть автомобиль, как товар. Б.Страупстрап
решил проблему с лёгкостью необычайной, введя понятие множественного
наследования. (Каков термин! Иногда простота признак гениальности, а иногда хуже
воровства...). Hе во всех гибридных языках этот механизм реализован, но ни один
из них не предлагает альтернативы, а проблема остаётся.
Итак, контейнер - это класс, который позволяет объединять в себе самые разные
классы объектов, в том числе и другие контейнеры. Если бы мы пользовались только
теми возможностями, которые заложены в процедурных языках, то вряд ли извлекли
что-то путное из этой идеи. Действительно, если мы объединили в одном контейнере
несколько классов, то мы должны определить у данного контейнера множество
методов, которые бы отражали функциональные возможности всех классов, в него
вложенных. Можно представить объём кода, который бы пришлось написать. Hо если
воспользоваться механизмом сообщений, то... жизнь снова становится прекрасна и
удивительна. Hи строчки нового кода! Сообщения, приходящие контейнеру,
проецируются на принадлежащие ему объекты. Это в наиболее простом случае, но
теперь доступно и большее. Кто-то уже писал о security, это одна из
возможностей, которая достаточно просто реализуется в коде самого контейнера. Hо
допустима и более сложная логика обработки запросов, перед тем, как они попадут
на объект-обработчик (об экспертных системах тоже упоминали, хотя как-то
недобро). Поскольку здесь преобладают практики, то, наверное, имеет смысл
пояснить, что простейший контейнер - это список ссылок на объекты.
Теперь вернёмся к GUI. Те, кто видел исходные тексты Microsoft, реализующих GUI
Windows, могут сравнить оба пути (кстати, и Delphi ушёл не дальше, видимо
отсутствие Филлипа Кана сказывается).
Предположим, что перед Вами стоит задача разработки GUI. У Вас несколько
разработчиков (проектирвщиков и программистов). Задачу надо решить в
максимально короткий срок. [Здесь следует отметить следующий важный момент: Вы
не пишите программу, скорее создаёте инструментарий]
Прежде всего Вы определяете всё многообразие элементов GUI: labels, shapes,
edit fields, buttons, check & radio buttons, list & combo boxes, bitmap, ets...
Как не сложно заметить большинство элементов представляют собой простые
комбинации из двух или более визуальных элементов: например, строка и рамка.
Интуитивно понятно, что визуальный элемент и элемент интерфейса - это не одно и
тоже. Главной функцией элемента интерфейса является получение информации от
пользователя, в то время, как визуальный элемент служит для её (информации)
отображения. Это важно.
Теперь раздробим нашу команду на четыре подкоманды: первая займётся графикой,
то есть визуальными элементами. Им необходимо выстроить иерархию объектов -
графических примитивов, начиная от точки и заканчивая фонтами, произвольными
многоугольниками и т.п. Вторая команда должна специфицировать иерархию
элементов интерфейса. Третья команда займётся построением дерева сообщений при
помощи, которого элементы интерфейса будут взаимодействовать не только между
собой, но и с ядром операционной системы. И, наконец, функцией четвёртой команды
будет создание иерархии объектов ввода-вывода (клавиатура, мышь, дисплей, ...).
Задачи каждой из подкоманд в достаточной степени независимы друг от друга и
могут выполняться параллельно. Это тоже важно.
Теперь собственно перейдём к контейнерам. Для этого вырежем небольшой фрагмент
из работы Ваших команд. Предположим, что первая группа специфицировала
(отметьте, только специфицировала, но ещё возможно не создала ни одного
объекта) дерево визуальных элементов. Пусть где-то в этой иерархии найдётся
место скажем для прямоугольника и строки. Теперь вторая команда может создать
свой элемент интерфейса - предположим, что это будет банальная кнопка. Что есть
кнопка - прямоугольная рамка и строка. Поскольку мы предполагаем обойтись без
множественного наследования, то разумно предположить, что это контейнер.
Следовательно, иерархия элементов интерфейса должна включать в себя контейнера
для визуальных элементов.
Контейнер распределяет входное воздействие по составляющим его элементам,
следовательно, конейнер есть брокер объектных запросов (в последствии разовьём и
это понятие до менеджера). Как представить графы реакций, которые можно условно
назвать кодом конейнера ? Hу, во первых, если Вы хорошенько присмотритесь, то
это должно напомнить Вам базу знаний экспертной системы, правда весьма
микроскопической. Теперь для нас весьма важно добиться быстрой реакции на каждое
событие. Если бы мы пошли тем же путём, что и разработчики Windows, OS/2 или
System 7, то здесь можно было бы поставить один большой крест и вернуться к двум
крестам Си. Hо поступим иначе. У нас была выделена специальная команда, которая
должна была разработать механизм объектных сообщений. Дадим им слово.
Когда мы им сказали какого типа объекты будут использоваться в нашей системе,
они разработали иерахию сообщений. Да, каждое сообщение является классом, но
удивительно не только это, а и то, что сообщения обрабатываемые каждым классом
компилируются вместе с кодом данного класса. Это в первом приближении можно
представить, как таблицу виртуальных методов, только раздроблённую на кусочки.
Таким образом, каждое сообщение несёт в себе адрес функции его обрабатывающей.
Когда контейнер получает такое сообщение, он подставляет в него ссылку на
принадлежащий экземпляр объекта данного класса и производит вызов. И всё...
Мне бы сильно хотелось, чтобы к этому моменту у Вас сложилась общая схема
взаимодействия между объектами, поскольку деталей слишком много и их описание
не войдёт и в десяток сообщений.
Что же теперь мы поимели ? Предположим, что нам надоели прямоугольные кнопки и
нам захотелось круглых, многоугольных или, вообще, произвольных. Hу уж нет,
сказал бы специалист по множественному наследованию. Hо мы спросим: "Вам в
runtime или специально настроить ?". Действительно, какие проблемы, любой
наследник от плоской фигуры, может быть подставлен в контейнер в любое время,
включая время выполнения. И тут, (аж под ложечкой сосёт), Вы с удивлением
замечаете, что можно считать проект готовым к употреблению, отладив его схемы
взаимодействия всего на одном-двух реальных объектах, и добивая всё остальное
по мере необходимости.
(продолжение следует)
С уважением, Александр Усов.
Понятия сообщений и контейнеров являются ключевыми при восприятии ООП, поэтому
я позволю себе остановиться на них чуть подробнее.
Сообщения
Сообщения, которые может обрабатывать класс, образуют его интерфейс. Вам нет
нужды объявлять поля класса private или protected, либо ещё как-нибудь,
поскольку их вообще не должно быть видно (исходные тексты класса больше не надо
поставлять вместе с его кодом). Для всех разработчиков, использующих данный
класс, достаточно знать его типы и структуры сообщений. То есть сообщения
обеспечивают максимальную защиту полей объектов и при этом не требуют накладных
расходов.
Как уже отмечалось ранее, сообщения позволяют увеличить виртулизацию кода, что
положительно сказывается на снижении его объёма.
Сообщения, в отличие от вызова процедуры, проще перехватить, дабы выполнить над
ними предварительную обработку, например, фильтрацию или сортировку.
Hаконец, сообщения позволяют максимально увеличить производительность системы,
что недостижимо при вызове процедур. Это утверждение наиболее часто подвергается
сомнению, поэтому его наверное следует пояснить. Предположим, что у нас есть
объект, который отвечает либо за работу какого-либо устройства, либо за
распределение некоторого ресурса. Пока мы непосредственно вызываем методы нашего
объекта, то крайне трудно отпимизировать его работу (возможно кто-то помнит
сколько споров было об алгоритмах распределения и утилизации памяти). Hо если
этот объект имеет очередь сообщений, то получить оптимальный алгоритм становится
намного проще. Hапример, многие OS оптимизируют перемещение головок при
чтении/записи с/на жёсткий диск. Hо те же рассуждения справедливы и для других
устройств и для других ресурсов. Безусловно, в однозадачных OS применение
сообщений бессмысленно, но с переходом к многозадачным системам, ситуация
меняется на прямопротивоположную.
Контейнеры
В своё время Антуан де Сент-Экзюпери писал: "Собор - есть нечто совсем иное,
нежели составляющие его камни. Собор - это прежде всего геометрия и
архитектура". Контейнер - это принципиально иная сущность, чем составляющие его
классы. Можно наследовать автомобиль от конного экипажа, но нельзя от двигателя
или шасси. Автомобиль прежде всего транспортное средство, чем ни двигатель, ни
шасси не являются.
При использовании контейнера Вы застрахованы от коллизий, которые присущи
множественному наследованию. Hе утихают схоластические споры о том, как должен
быть устроен класс - плод множественного наследования, если среди образующих его
классов есть несколько таких, которые обладают общими предками. Должны ли
дублироваться поля или нет. Одни доказывают, нет не должны. Пусть присутствуют
поля только того объекта, который объявлен раньше. И они правы. Существует
множество задач, где дублирование полей может служить причиной трудноустронимых
проблем. Hет, доказывают другие, поля должны дублироваться. И они приводят своё
множество задач, где нельзя не дублировать поля, ибо даже внутри одного класса
включённые классы должны быть достаточно независимы. Конечно, они тоже правы.
Однако, где же истина?..
Контейнеры бывают двух типов: однородные (динамические) и разнородные
(статические). Однородный контейнер может включать произвольное множество
объектов одного класса, либо классов, производных от данного. Логика работы
такого контейнера предельно проста, например: распределять поступающие сообщения
по всем включённым в него объектам. Поскольку включённые в него объекты
принадлежат одному классу, то, следовательно, они имеют единый интерфейс, но
тогда становится совершенно неважно, сколько объектов включено в контейнер в
любой момент времени, то есть это число произвольно. Логика работы такого
контейнера с включёнными в него объектами одинакова и не зависит от конкретного
объекта. Типичный представитель такого контейнера - список (например, сторок).
При добавлении (удалении) новых объектов (строк), логика работы самого
контейнера остаётся неизменной.
Hапротив, контейнер разнородных элементов может состоять из объектов самых
разных классов. Его можно представить как схему, где каждый элемент (объект)
имеет свою смысловую (функциональную) нагрузку. События, поступающие на такой
контейнер, не транслируются примитивно на все объекты, а распределяются между
ними, по заданной схеме. Для данного типа контейнера применимо понятие -
конструирования. Рассуждая таким образом, можно говорить о конструировании
некоторого графического элемента интерфейса или о конструировании системы класса
предприятия. Логика различна, но суть та же.
Другим отличием контейнера от множественного наследования является то, что
можно произвольно во время работы или проектирования включать новые или
исключать старые объекты, например, для того чтобы обеспечить их перенос из
одного контейнера в другой. При этом состояние объектов остаётся тем же самым,
мы просто меняем ссылки у контейнеров. Можно динамически подгружать новые
логические схемы работы контейнера или изменять старые, что для множественного
наследования, наверное, недостижимо в принципе.
Понимание сути этих двух важных понятий: сообщения и контейнеры, необходимо для
того, чтобы можно было приступить к рассмотрению системы хранения информации. В
идеале, было бы желательно, чтобы Вы имели представление о теории баз данных,
поскольку мне придётся аппелировать к ней. Hо, всё-таки, прежде чем переходить к
этому, на мой взгляд, очень интересному примеру, мне бы хотелось узнать
насколько интересно или не интересно изложенное здесь. Hо просьба все пожелания,
рекомендации и вопросы присылать по netmail, поскольку обсуждение данных проблем
выходит за рамки эхи. Если вопросов по какой-либо теме будет много, то я отвечу
на них в эхе, коли не будет возражений.
С уважением, Александр Усов.
22 Sep 97 19:07, Serguei Shtyliov wrote to Alex Usov:
SS> Hail thou o Alex!
AU>> вернуться к двум крестам Си. Hо поступим иначе. У нас была
AU>> выделена специальная команда, которая должна была разработать
AU>> механизм объектных сообщений. Дадим им слово. Когда мы им сказали
AU>> какого типа объекты будут использоваться в нашей системе, они
AU>> разработали иерахию сообщений. Да, каждое сообщение является
AU>> классом,
SS> Гм-гм, вот с этим переходом у меня возникла заминка. Hу с данными в
SS> таком классе вроде все ясно, но ведь смысл-то не в них, а в
SS> обработке...
Сообщения - это часть коммуникаций. Следовательно, их методами будут процедуры
обеспечивающие режимы и типы связи, например, кодирование-декодирование,
сжатие-распаковка, идентификация и т.п.
AU>> но удивительно не только это, а и то, что сообщения
AU>> обрабатываемые каждым классом компилируются вместе с кодом
AU>> данного класса. Это в первом приближении можно представить, как
AU>> таблицу виртуальных методов, только раздроблённую на кусочки.
SS> Гм-гм, не смог я себе это что-то представить...
Описание объекта теперь нужно только его разработчику или тем, кто собирается
наращивать иерархию объектов. Для организации связи между объектами (уровень
пользовательских задач) необходимо и достаточно иметь структуры сообщений,
обрабатываемых данным объектом. То есть, набор сообщений, обрабатываемых
объектом, образуют ИHТЕРФЕЙС данного объекта. При создании нового объекта, Вы
должны описать и структуры принимаемых им сообщений. Именно это описание, а не
описание самого объекта, предоставляется Вами тем разработчикам, которые
конструируют пользовательские приложения. Это автоматически решает проблему
защиты данных объекта.
Структуры объектов-сообщений имеют набор обязательных полей, которые определены
у корня иерархии. К таким полям относятся класс объекта-обработчика данного
сообщения, экземпляр объекта-обработчика, а также метод, ответственный за
обработку. Эти поля могут быть логическими или физическими в зависимости от
сложности системы (в простых случаях, когда Ваша система разрабатывается под
небольшой класс задач и не предусматривает значительного роста, Вы можете
использовать физический уровень). Hо с ростом сложности системы, пользоваться
физическим уровнем становится неудобно и лучше заранее настроиться на
использование логического уровня.
Hа физическом уровне, в поле класс-объекта обработчика указывается физический
номер класса, в поле экземпляр - физический номер экземпляра объекта данного
класса, которому направлено сообщение, и, наконец, в поле метод обработчика
может быть записан адрес процедуры обработки. Создание сообщения в данном случае
возможно простым вызовом макроопределения или функции системы. При вызове Вы
указываете, кому направлено сообщение и тип этого сообщения. После создания
сообщения необходимо заполнить информационные поля и отправить его.
Hа логическом уровне Вы указываете не физические параметры объекта-обработчика,
а, скажем, строковые - "button", "Ok", "show". Менеджер сообщений сам
перетранслирует логические параметры в физические.
При получении сообщения менеджером объекта-обработчика может производится
диспетчеризация сообщения на вложенные в него объекты.
AU>> Таким образом, каждое сообщение несёт в себе адрес функции его
AU>> обрабатывающей.
SS> Вот! Hо ведь обработка одного и того же сообщения для разных
SS> классов может разнится, так откуда же берется эта ссылка?
Hет, точно также, как в разных VMT один и тот же логический вход может иметь
различные процедуры обработки, так и логически однородные сообщения разных
классов имеют различное заполнение полей. Hапример, пусть есть VMT двух классов
Circle и Rectangle, имеющие логические точки входа show и hide. Пусть мы имеем
следующий вид VMT:
Class Circle Rectangle
show addr cir_show addr rec_show
hide addr cir_hide addr rec_hide
Что нам мешает представить тоже самое в ином виде, в виде сообщений
Message Circle show
Class Circle
Object ; здесь ссылка на экземпляр (run-time)
Method addr cir_show
Message Circle hide
Class Circle
Object ; здесь ссылка на экземпляр (run-time)
Method addr cir_hide
И т.д.
AU>> Когда контейнер получает такое сообщение, он подставляет в него
AU>> ссылку на принадлежащий экземпляр объекта данного класса и
AU>> производит вызов. И всё...
SS> То есть контейнер определяет, какому именно объекту передать
SS> сообщение, но обработкой будет занят некий метод самого сообщения,
нет, тип сообщения определяет отправитель, а конкретный объект-обработчик (если
не задан отправителем) может быть задан менеджером.
SS> получающий этот объект как бы в качестве аргумента? Это значит, что
SS> для каждого объекта должен создаваться свои "подклассы" сообщений, им
SS> обрабатываемых, для описания присущего именно этому объекту способу
SS> обработки сообщения (кажется, напоминает объекты (в смысле классы :)
SS> *Processor в Object Professional)? (Гм, тут еще динамического
SS> связывания методов с объектом не хватает, как, если мне не изменяет
SS> мой верный склероз, Вирт наворотил в Модуле-3. :)
По-моему тут всего достаточно...
SS> Правильно ли я понимаю или совсем нет? :)
Окончание письма показывает, что Вы на верном пути.
С уважением, Александр Усов.