Нестандартное использование объектно-ориентированного программирования

Нестандартное использование объектно-ориентированного программирования

Объекты типа Variant

Рассматривая применение объектно-ориентированного подхода в программировании, можно заметить тенденцию - классы редко рассматриваются как самостоятельные элементы программы. Обычно классы формируют библиотеку классов для решения каких-то локальных задач.

Сам набор свойств объекта не несет в себе информацию о природе объекта. Обычно природа объекта становится принципиальна при исполнении методов класса. Интерпретация назначения свойств класса определяется содержимым методов класса и осуществляется на уровне компилятора.

При проектировании интерфейса классов считается, что ветвление на основе одного свойства по идентификации сущности объекта недопустимо. Ветвление исключается путем переопределения методов класса. Рекомендуется осуществить наследование от данного класса нескольких классов, определяемых количеством вариантов ветвления.

Особенно неудобно использование большого количества классов при работе со списковыми моделями приложений - моделями, содержащими списки ресурсов различных классов. Одновременное использование нескольких списков создает громоздкость программы. Когда используемых классов много, то существует лишь возможность объединения ресурсов в один список на базе списка ссылок на общий класс-предок. Использование этого метода возможно только при наличии ветвления на основе идентификатора класса ресурса. В этом случае ветвление переносится из клиентского класса в родительский класс. Этот подход можно продемонстрировать на работе со списком компонент форм в Delphi и Visual Basic с применением функций is и as.

При создании экземпляра класса на основании существующей ссылки на класс-предок идет разговор лишь о возможности выбора класса объекта при его создании (вызов конструктора класса). Для создания объекта, экземпляра другого класса, необходимо разрушить старый объект и создать новый, но в этом случае идет разрушение данных внутри объекта. В принципе это уловка, позволяющая как-то осуществлять выбор сущности объекта перед его созданием, а не операция динамического изменения сущности объекта. Мы пойдем дальше и выделим классы, реализующие возможность изменения сущности объекта, в особую группу.

При анализе ряда классов близких по своей природе (по сущности) можно выявить лишь незначительные отличия некоторых методов или свойств, что дает предпосылки для объединения эти классов в один универсальный.

Это дает существенное преимущество при создании картографических редакторов, библиотек визуальных компонент, редакторов векторной графики, игр, библиотек стилей и т.д.

Для возможности различия поведения сущностей в универсальном классе введем свойство атрибут - идентификатор сущности. Благодаря этому свойству появится возможность различать сущности, описываемые этим классом, а следовательно и поведение экземпляра данной сущности.

Миграция сущности происходит при использовании стандартного способа наследования классов. В данном случае происходит статическая миграция сущности класса предка на сущность класса потомка.

Наиболее важный момент при проектировании атрибутивного класса - правильно определить шаблон будущего класса - набор свойств класса, который бы удовлетворял всем реализованным сущностям. Возможно, не все сущности будут использовать весь набор существующих свойств атрибутивного класса, но надо стремиться к тому, чтобы количество этих свойств было минимально. Важно определить набор основных методов класса. Необходимо, чтобы количество недопустимых методов (бесполезных) для различных сущностей было минимально.

Рассмотрим пример применения атрибутивного объекта для рисования графических объектов: точки, линии, прямоугольника, круга, эллипса. Аналогичный пример с наследованием классов и образованием простой библиотеки графических классов обычно рассматривают в начале литературы, посвященной ООП.

Для непосвященных: обычно любой класс имеет данные - свойства класса и код (функции) - методы класса. Это является отличием структур от классов и является одним из 3-х свойств классов - инкапсуляцией. Структура - это набор переменных логически связанных между собой и позволяющих описать какой-либо объект или явление. В классах присутствует еще методы - набор команд по обработке данных находящихся в классе.

Выделим набор свойств будущего класса. Любой набор свойств атрибутивного класса включает поле Attribute - идентификатор сущности. Для этого необходимо проанализировать набор данных для каждого типа объекта в конкретности. В целях упрощения примера не будем рассматривать управление цветом графического примитива. Предполагаем, что достаточно только осуществлять отрисовку контура графического примитива. Проанализируем, какие поля требуются для функционирования описываемых атрибутивным классом сущностей:

• точка (координаты X, Y);

• линия (координаты начальной точки X, Y, координаты конечной точки X2,Y2);

• прямоугольник (либо координаты одного угла прямоугольника X,Y и координаты противоположного угла X2,Y2, либо часто используют координаты верхнего левого угла X,Y, ширину W и высоту H прямоугольника);

• круг (координаты центра окружности X, Y, радиус окружности R);

• эллипс (координаты центра окружности X, Y, большая R1 и малая R2 полуоси).

Во всех сущностях используются координаты точки X, Y. Эти координаты являются определяющими для местоположения объекта любой описываемой сущности, поэтому эти свойства включаются в шаблон. Сущность точки из дальнейшего рассмотрения можно исключить. Среди оставшихся сущностей можно выделить две группы близких по структуре: линия и прямоугольник (наличие двух крайних точек), круг и эллипс (наличие радиусов-полуосей). Проанализируем возможности по объединению полученных групп. Наиболее рациональным вариантом является использование координат базовой точки и рисование графического примитива относительно ее, тогда "смещение" контура объекта:

• для прямоугольника - высота H и ширина W;

• для эллипса - полуоси H и W;

• для окружности - радиус H или W (R=H=W).

Если взять за основу полученный шаблон X, Y, H, W, то надо пересмотреть свойства линии: либо считать H и W координатами второй точки, либо считать H и W смещением координат второй точки по горизонтали и вертикали от координат базовой точки. Последний вариант предпочтительнее, т.к. в наибольшей степени преодолевает двойственность назначения полей H и W. Тогда полученный шаблон имеет следующие поля целого типа: Attribute (идентификатор), X и Y (координаты базовой точки), H и W (смещение).

В рассмотренном примере все свойства имеют одинаковый тип, и в принципе это идеальный вариант. Когда оказывается, что невозможно объединить структуры на основе анализа назначения полей (возможно, их названий), то приходится вести объединение на основе типов полей. Необходимо, в этом случае, максимально исключить двойственность назначения полей.

Можно было бы пойти по пути, предложенному визуальными библиотеками, т.е. рассматривать все объекты как окна с прозрачным фоном и нарисованными на них графическими фигурами. В качестве начального шаблона для MFC Microsoft выступает класс CObject и для OWL Borland - класс TObject, т.е. все объекты объединяются на основе признака окна и вся отрисовка ориентируется на "врисовывание" любого графического объекта в это окно.

Попробуем определить основные методы проектируемого класса:

• конструктор - будет задавать начальные значения полей;

• функция moveTo - изменение положения объекта на экране компьютера, параметры - координаты базовой точки;

• функция paint - нарисовать объект, без параметров;

• функция change - изменить значение свойства Attribute и осуществить перерисовку объекта, принимаемый параметр - новые значения идентификатора сущности.

Лишь один метод paint имеет ветвление на базе свойства Attribute. Я не буду приводить листинг класса и программы, использующей данный класс, т.к., во-первых, он занял бы много место в статье, во-вторых, я бы не хотел привязываться к среде разработки, и в-третьих, любой программист, обладающий некоторыми навыками разработки графических программ, в состоянии воспроизвести данный код.

Абзац для гуру: для возможности сохранения чистоты линии ООП возможно создание класса-контейнера, в котором хранятся значения полей объекта. На основе класса-контейнера пронаследовать набор классов по числу описываемых примитивов. При этом, метод paint необходимо сделать виртуальным и вызов его осуществлять на основе ссылки на класс-предок. И это все создавать для чистоты линии? При этом нет возможности менять тип объекта без разрушения данных. Безусловно, их можно копировать и т.д. Проще - напишите маленький пример и разница трудоемкости будет ощутима...

Гораздо полезнее поговорить об ускорении интерпретации полей объекта. Методы, имеющие ветвление на базе свойства Attribute, можно делить на отдельные функции и представлять в виде процедурного типа. Закрепление функций - выбор вызываемой функции будет осуществляться один раз путем ветвления в конструкторе и в функции, изменяющей сущность объекта, а не при каждом вызове метода, что резко увеличит скорость выполнения приложений, использующих такие классы.

По характеру поведения класса атрибутивного объекта класс можно классифицировать как интерпретатор значений свойств объекта на основании свойства атрибут. Мало кто обращал внимание на то, что операционная система, на которой он работает (Windows), тоже интерпретатор событий. И описатель события Windows в принципе такой же атрибутивный объект. Такой же механизм реализации обработки вызовов прерываний BIOS и DOS. Почему? Существует ярко выраженное наличие шаблона - списка фиксированных свойств класса (параметров описываемых объектов) и наличие идентификатора назначения (атрибута) - номер функции и прерывания. Для события Windows идентификатор сообщения - код сообщения Windows, значения которого обычно описываются константами, начинающимися с WM_. Параметры сообщения - структура, описывающая сообщение. Для большего понимания сути изложенного можете обратиться к документации по функциям API GetMessage, PeekMessage, PostMessage и т.д. Таким образом, вы уже сталкивались с объектами, обладающими возможностью миграции сущности (назначения), но не рассматривали их с точки зрения объектно-ориентированной концепции программирования.

Существует попытка применения атрибутивных классов на низком уровне в качестве универсальных типов данных. Возможно, вы уже сталкивались с типом Variant. До последнего времени это было характерно только для Visual Basic, но теперь этот тип появился и в новых версиях Delphi. По сути, тип Variant - это атрибутивный класс с переопределенными операторами присваивания, арифметическими и логическими операторами. Переопределенные операторы настраивают атрибутивный класс к обработке определенного типа данных. Функционирование этого класса скрыто от пользователя. Формально, атрибутивный класс - это класс типа Variant, но для сущностей более высокого порядка.

P.S. А теперь по сути - зачем я пытался рассказать? Для того, что бы на основе этой технологии писать каркасы игр... Если у кого появились идеи или желание - пишите.

Кстати, эта технология породила еще двух монстров: мультиязычный интерпретатор ALL - любая программа может быть написана на нескольких языках программирования и "универсальную модель данных" (2-е золотое сечение) - проекцию всех 4-х основных моделей баз данных на реляционную модель и конкретно - объектно-ориентированную базу данных Omnibus. Сергей Соколов (БГУИР) sokol@belcaf.minsk.by (c) компьютерная газета


Компьютерная газета. Статья была опубликована в номере 34 за 2000 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета