Переносимость и адаптация к платформе
Некоторые разработчики приложений предпочитают переносимые библиотеки, позволяющие написать один исходный текст системы. Для переноса системы на другую платформу достаточно ее перекомпилировать, не внося никаких изменений. Другие хотели бы обратного: получить полный доступ ко всем специфическим элементам управления и прочим "штучкам" конкретной платформы, например, Microsoft Windows, но в удобном виде (а не на низком уровне стандартных библиотек). Третьи хотели бы понемногу и того и другого: переносимости по умолчанию и возможности, если потребуется, стать "родным" для данной платформы.
При аккуратном проектировании, основанном на двухуровневой структуре, можно попытаться удовлетворить все три группы:
Рис. 14.1. Архитектура графической библиотеки
Для конкретизации на рисунке приведены имена соответствующих компонентов из окружения ISE, но идея применима к любой графической библиотеке. На верхнем уровне (Vision) находится переносимая графическая библиотека, а на нижнем уровне - специализированные библиотеки, такие как WEL для Windows, каждая из них приспособлена к "своей" платформе.
WEL и другие библиотеки нижнего уровня можно использовать непосредственно, но они также служат как зависящие от платформы компоненты верхнего уровня: механизмы Vision реализованы посредством WEL для Windows, посредством MEL для Motif и т. д. У такого подхода несколько преимуществ. Разработчикам приложений он дает надежду на совместимость понятий и методов. Разработчиков инструментальных средств он избавляет от ненужного дублирования и облегчает реализацию высокого уровня, базирующуюся не на прямом всегда опасном интерфейсе с C, а на ОО-библиотеках, снабженных утверждениями и наследованием, таких как WEL. Связь между этими двумя уровнями основана на описателях (см. лекцию 6).
У разработчиков приложений имеется выбор:
- Для обеспечения переносимости следует использовать верхний уровень. Он также представляет интерес для разработчиков, которые, даже работая для одной платформы, хотят выиграть от более высокой степени абстракций, предоставляемых такими библиотеками высокого уровня, как Vision.
- Для получения прямого доступа ко всем специфическим механизмам некоторой платформы (например, многочисленным элементам управления, предоставляемым Windows NT), следует перейти на соответствующую библиотеку нижнего уровня.
Рассмотрим один тонкий вопрос.
Как много специфических для данной платформы возможностей допустимо потерять при использовании переносимой библиотеки? Корректный ответ на него является результатом компромисса. Некоторые из первых переносимых библиотек использовали подход пересечения ("наименьшего общего знаменателя"), ограничивающий предлагаемые возможности теми, которые предоставляются всеми поддерживаемыми платформами. Как правило, этого недостаточно. Авторы библиотек могли использовать и противоположный подход - объединения: предоставить каждый из механизмов каждой из поддерживаемых платформ, используя точные алгоритмы для моделирования механизмов, первоначально отсутствующих на той или иной платформе. Такая политика приведет к огромной и избыточной библиотеке. Правильный ответ находится где-то посередине: для каждого механизма, присутствующего не на всех платформах, авторы библиотеки должны отдельно решать, достаточно ли он важен для того, чтобы промоделировать его на всех платформах. Результатом должна явиться согласованная библиотека, достаточно простая, чтобы использоваться без знаний особенностей отдельных платформ, и достаточно мощная для создания впечатляющих графических приложений.
Для разработчиков приложений еще одним критерием в выборе между двумя уровнями служит эффективность. Если основной причиной выбора верхнего уровня служит абстрактность, а не переносимость, то можете быть уверены - включение дополнительных классов приведет к потерям в памяти. Для правильно спроектированных библиотек потерями времени можно обычно пренебречь. Поэтому эффективность по памяти определяет, нужно ли это делать. Ясно, что библиотека для одной платформы (например, WEL) будет более компактной.
Наконец, заметим, что оба решения не являются полностью взаимоисключающими. Можно сделать основную часть работы на верхнем уровне, а затем добавить "бантики" для пользователей, работающих с библиотекой для самой продаваемой платформы. Конечно, это следует делать аккуратно, беззаботное смешение переносимых и непереносимых элементов быстро ликвидирует любую ожидаемую выгоду от переносимой разработки.
Один элегантный образец проекта (используемый ISE в некоторых своих библиотеках) основан на попытке присваивания (см. лекцию 16 курса "Основы объектно-ориентированного программирования"). Его идея в следующем. Рассмотрим некоторый графический объект, известный через сущность m, тип которой определен на верхнем уровне, например, MENU. Всякий актуальный объект, к которому она будет присоединяться во время исполнения, будет, конечно, специфическим для платформы, т. е. будет экземпляром некоторого класса нижнего уровня, скажем, WEL_MENU. Для применения специфических для платформы компонентов требуется некоторая сущность этого типа, скажем wm. Далее можно воспользоваться следующей схемой:
wm ?= m if wm = Void then ... Мы не под Windows! Ничего не делать или заниматься другими делами... else ... Здесь можно применить к wm любой специфический для Windows компонент WEL_MENU ... endМожно описать эту схему, как путь в комнату Windows. Эта комната закрыта, не позволяя утверждать, если кто-нибудь вас в ней обнаружит, что вы попали туда случайно. Вам разрешается в нее войти, но для этого вы должны открыто и вежливо попросить ключ. Попытка присваивания является официальной просьбой разрешения войти в область специального назначения.