Технологии программирования
Шрифт:
1. Класс_1 в нашей иерархии соответствует ветви TObject → TPersistent → TComponent;
2. Класс_2 — TControl —> TWinControl;
3. Класс_3 — TbuttonControl;
4. Класс_4 — TCustomListBox.
Если с двумя последними классами все понятно, то возникает вопрос: почему первые два класса нашей иерархии соответствуют не одному, а целым цепочкам классов C++ Builder? Дело в том, что в рассматриваемом примере описаны не все классы C++ Builder. Поэтому те из них, которые приводят к разветвлению двух первых цепочек, здесь просто не учтены. Например, элемент TImage, предназначенный для расположения на формах графических изображений, имеет следующую цепочку наследования классов: TObject → TPersistent → TComponent → TControl → TGraphicsControl, —
Рис. 8.12. Предварительная иерархия классов
Рис. 8.13. Иерархия некоторых классов C++ Builder
Рис. 8.14. Фрагмент схемы иерархии
8.11. АЛЬТЕРНАТИВНЫЙ ПРОЕКТ ГРАФИЧЕСКОГО ИНТЕРФЕЙСА
При развитии программ постоянно возникает проблема увеличения функциональных возможностей одного объекта за счет функциональных возможностей другого. Актуальнейшая проблема программирования — написание гибких программ, приспособленных для модификации и развития.
Вначале надо ввести всего одно понятие, предложенное Александром Усовым: контейнер-менеджер, или просто контейнер. Следует отметить, что здесь не идет речь о контейнере C++. Итак, контейнер — это класс, который позволяет объединять (агрегировать) в себе самые разные классы объектов, в том числе и другие контейнеры. Одной из наиболее сложных задач проектирования является агрегация разнородных элементов в новое единое целое. Контейнер — один из механизмов решения проблемы гибкой агрегации.
Простейший контейнер — это список ссылок на объекты. Далее если воспользоваться механизмом сообщений, то… всех этих затруднений можно избежать! Ни строчки нового кода! Сообщения, приходящие контейнеру, проецируются на принадлежащие ему объекты. Но допустима и более сложная логика обработки запросов, перед тем как они попадут к объекту-обработчику.
Сообщения, которые может обрабатывать класс, образуют его интерфейс. При использовании таких контейнеров нет нужды объявлять поля класса private или protected либо еще как-нибудь, поскольку их вообще не должно быть видно (исходные тексты класса больше не надо поставлять вместе с его кодом). Для всех разработчиков, использующих данный класс, достаточно знать его типы и структуры сообщений, т. е. сообщения обеспечивают максимальную защиту полей объектов и при этом не требуют накладных расходов.
Сообщения позволяют увеличить виртуализацию кода, что положительно сказывается на снижении его объема. Сообщения в отличие от вызова процедуры проще перехватить, дабы выполнить над ними предварительную обработку, например фильтрацию или сортировку. Наконец, сообщения позволяют максимально увеличить производительность системы, что недостижимо при вызове процедур.
Контейнеры бывают двух типов: однородные (динамические) и разнородные (статические).
Однородный контейнер может включать произвольное множество объектов одного класса либо классов, производных от данного класса. Логика работы такого контейнера предельно проста, например распределять поступающие сообщения по всем включенным в него объектам. Поскольку включенные в него объекты принадлежат одному классу, то, следовательно, они имеют единый интерфейс, но тогда становится совершенно
Напротив, контейнер разнородных элементов может состоять из объектов самых разных классов. Его можно представить как схему, где каждый элемент (объект) имеет свою смысловую (функциональную) нагрузку. События, поступающие на такой контейнер, не транслируются примитивно на все объекты, а распределяются между ними по заданной схеме. Для данного типа контейнера применимо понятие "конструирование".
Другим отличием контейнера от множественного наследования является то, что можно произвольно во время работы или проектирования включать новые или исключать старые объекты, например, для того чтобы обеспечить их перенос из одного контейнера в другой. При этом состояние объектов остается тем же самым, мы просто меняем ссылки у контейнеров. Можно динамически подгружать новые логические схемы работы контейнера или изменять старые, что для множественного наследования, наверное, недостижимо в принципе. Следовательно, контейнер может гибко реализовывать полиморфизм в наиболее общем смысле!
Отметим еще раз, что взаимосвязь между объектами осуществляется посредством сообщений. Но здесь сообщения — специальный класс. Именно этот класс несет ответственность за полиморфизм свойств, но никак не классы основной иерархии. В таком случае у нас есть возможность объявить некоторый класс-сообщение и создать набор полиморфных классов-наследников, которые будут обрабатываться объектами основной иерархии классов.
Удобство работы с сообщениями вовсе не означает, что можно менять (добавлять или модифицировать) набор свойств класса основной иерархии. Нет, свойства каждого класса задаются на этапе проектирования иерархии.
При использовании контейнеров ни в одном объекте не используются ни конструкторы, ни деструкторы. Это не случайно. В чем суть конструктора? Реально он должен выполнить два действия: проинициализировать указатель на таблицу виртуальных методов (VMT) и проинициализировать собственные данные.
Рассмотрим пример проекта с использованием контейнеров. Предположим, что перед вами стоит задача разработки графического интерфейса, аналогичного GUI Microsoft Windows. Аналогичный интерфейс создавали разработчики Delphi, и ранее мы ретроспективно выполняли данный проект.
У вас несколько разработчиков (проектировщиков и программистов), и задачу надо решить в максимально короткий срок. Здесь следует отметить следующий важный момент: вы не сразу пишете программу, а скорее создаете инструментарий ее решения.
Прежде всего вы определяете все многообразие элементов GUI: labels, shapes, edit fields, buttons, check radio buttons, list combo boxes, bitmap и т. д. Несложно заметить, что большинство элементов представляет собой простые комбинации из двух или более визуальных элементов: например строка и рамка. Интуитивно понятно, что визуальный элемент и элемент интерфейса — это не одно и то же. Главной функцией элемента интерфейса является получение информации от пользователя, в то время как визуальный элемент служит для ее (информации) отображения. Это важно.
Теперь раздробим нашу команду на четыре подкоманды.
Первая команда займется графикой, т. е. визуальными элементами. Им необходимо выстроить иерархию объектов — графических примитивов, начиная от точки и заканчивая фонтами, произвольными многоугольниками и т. п.
Вторая команда должна специфицировать иерархию элементов интерфейса.
Третья команда займется построением дерева сообщений, при помощи которого элементы интерфейса будут взаимодействовать не только между собой, но и с ядром операционной системы.