229
ProgramNodeBuilder builder;
Parser parser;
parser.Parse(scanner, builder);
RISCCodeGenerator generator(output);
ProgramNode* parseTree = builder.GetRootNode();
parseTree->Traverse(generator);
}
В этой реализации жестко «зашит» тип кодогенератора, поэтому програм- мисту не нужно явно задавать целевую архитектуру. Это может быть вполне разумно, когда такая архитектура всего одна. Если же это не так, можно было бы изменить конструктор класса
Compiler
, чтобы он принимал объ- ект
CodeGenerator в качестве параметра. Тогда программист указывал бы, каким генератором пользоваться при создании экземпляра
Compiler
. Фасад компилятора можно параметризовать и другими участниками, скажем, объ- ектами
Scanner и
ProgramNodeBuilder
, что повышает гибкость, но в то же время сводит на нет основную цель фасада — предоставление упрощенного интерфейса для наиболее распространенного случая.
Известные применения
Пример с компилятором в разделе «Пример кода» навеян идеями из системы компиляции языка ObjectWorks\Smalltalk [Par90].
В каркасе ET++ [WGM88] приложение может иметь встроенные средства инспектирования объектов во время выполнения. Они реализуются в отдель- ной подсистеме, включающей класс фасада с именем
ProgrammingEnvironment
Этот фасад определяет такие операции, как
InspectObject и
InspectClass для доступа к инспекторам.
Приложение, написанное в среде ET++, может также запретить поддержку инспектирования. В таком случае класс
ProgrammingEnvironment реализует соответствующие запросы как пустые операции, не делающие ничего. Только подкласс
ETProgrammingEnvironment реализует эти операции так, что они отображают окна соответствующих инспекторов. Приложению неизвестно, доступно инспектирование или нет. Здесь мы встречаем пример абстрактной связанности между приложением и подсистемой инспектирования.
В операционной системе Choices [CIRM93] фасады используются для со- ставления одного каркаса из нескольких. Ключевыми абстракциями в систе- ме Choices являются процессы, память и адресные пространства. Для каждой из них есть соответствующая подсистема, реализованная в виде каркаса.
Это обеспечивает поддержку переноса Choices на разные аппаратные плат-
230 Глава 4. Структурные паттерны формы. У двух таких подсистем есть «представители», то есть фасады. Они называются
FileSystemInterface
(подсистема хранения данных) и
Domain
(адресные пространства).
PagedMemoryObjectCacheMemoryObjectCacheAddressTranslationFindMemory(Address)
TwoLevelPageTableMemoryObjectBuildCache()
PersistentStoreFileDiskProcessAdd(Memory, Address)
Remove(Memory)
Protect(Memory, Protection)
RepairFault()
DomainКаркасвиртуальнойпамятиНапример, для каркаса виртуальной памяти фасадом служит класс
Domain
, представляющий адресное пространство. Он обеспечивает отображение между виртуальными адресами и смещениями объектов в памяти, файле или на устройстве длительного хранения. Базовые операции класса
Domain поддерживают добавление объекта в память по указанному адресу, удаление объекта из памяти и обработку ошибок отсутствия страниц.
Как
видно из вышеприведенной диаграммы, внутри подсистемы виртуаль- ной памяти используются следующие компоненты:
MemoryObject представляет объекты данных;
MemoryObjectCache кэширует данные из объектов
MemoryObjects в физи- ческой памяти.
MemoryObjectCache
— это не что иное, как объект cтрате- гия (362), в котором локализована политика кэширования;
AddressTranslation инкапсулирует особенности оборудования транс- ляции адресов.
Паттерн Flyweight (приспособленец)
231
Операция
RepairFault вызывается при возникновении ошибки из-за от- сутствия страницы в памяти.
Domain находит объект в памяти по адресу, по которому произошла ошибка, и делегирует операцию
RepairFault кэшу, ассоциированному с этим объектом. Поведение объектов
Domain можно на- строить, заменив их компоненты.
Родственные паттерны
Паттерн абстрактная фабрика (113) допустимо использовать вместе с фаса- дом, чтобы предоставить интерфейс для создания объектов подсистем спосо- бом, независимым от этих подсистем. Абстрактная фабрика может выступать и как альтернатива фасаду, чтобы скрыть платформеннозависимые классы.
Паттерн посредник (319) аналогичен фасаду в том смысле, что абстрагирует функциональность существующих классов. Однако назначение посредника — абстрагировать произвольное взаимодействие между «сотрудничающими» объектами. Часто он централизует функциональность, не присущую ни одному из них. Коллеги посредника знают о его существовании и обмени- ваются информацией именно с ним, а не напрямую между собой. С другой стороны, фасад просто абстрагирует интерфейс объектов подсистемы, чтобы ими было проще пользоваться. Он не определяет новой функциональности, и классам подсистемы ничего неизвестно о его существовании.
Обычно в системе должен существовать только один фасад, поэтому объекты фасадов часто бывают одиночками (157).
ПАТТЕРН FLYWEIGHT (ПРИСПОСОБЛЕНЕЦ)
Название и классификация паттерна
Приспособленец — паттерн, структурирующий объекты.
Назначение
Применяет совместное использование для эффективной поддержки мно- жества мелких объектов.
Мотивация
В некоторых приложениях использование объектов могло бы быть очень полезным, но прямолинейная реализация оказывается недопустимо рас- точительной.
232 Глава 4. Структурные паттерны
Например, в большинстве редакторов документов имеются средства фор- матирования и редактирования текстов, в той или иной степени модуль- ные. Объектно-ориентированные редакторы обычно применяют объекты для
представления таких встроенных элементов, как таблицы и рисунки.
Но они не применяют объекты для представления каждого символа, не- смотря на то что это увеличило бы гибкость на самых нижних уровнях приложения — ведь тогда символы и встроенные элементы можно было бы прорисовывать и форматировать по единым принципам, и для поддержки новых наборов символов не пришлось бы как-либо затрагивать остальные функции редактора. Вдобавок общая структура приложения отражала бы физическую структуру документа. На следующей диаграмме показано, как редактор документов мог бы воспользоваться объектами для пред- ставления символов.
У такого дизайна есть один недостаток — затраты ресурсов. Даже в докумен- те скромных размеров было бы несколько сотен тысяч объектов-символов, а это привело бы к расходованию огромного объема памяти и неприемлемым затратам во время выполнения. Паттерн приспособленец показывает, как совместно использовать очень мелкие объекты без недопустимо высоких затрат.
ОбъектысимволыОбъектыстрокиОбъектыколонки Паттерн Flyweight (приспособленец)
233Приспособленец — это совместно используемый объект, который можно задействовать одновременно в нескольких контекстах. В каждом контексте он выглядит как независимый объект, то есть неотличим от экземпляра, который не используется совместно. Приспособленцы не могут делать предположений о контексте, в котором работают. Ключевая идея здесь — различие между
внутренним и
внешним состояниями. Внутреннее со- стояние хранится в самом приспособленце и состоит из информации, не зависящей от его контекста. Именно поэтому он может использоваться совместно. Внешнее состояние зависит от контекста и изменяется вместе с ним, поэтому совместно не используется. Объекты-клиенты отвечают за передачу внешнего состояния приспособленцу, когда в этом возникает необходимость.
Приспособленцы моделируют концепции или сущности, число которых слиш- ком велико для представления объектами. Например, редактор документов мог бы создать по одному приспособленцу для каждой буквы алфавита.
Каждый приспособленец хранит код символа, но координаты положения символа в документе и стиль его начертания определяются алгоритмами размещения текста и командами форматирования, действующими в том месте, где символ появляется. Код символа — это внутреннее состояние, а все остальное — внешнее.
На логическом уровне для каждого вхождения данного символа в документ существует объект.
Cтрока
Cтрока
Cтрока
Колонка
Однако на физическом уровне существует лишь по одному объекту-при-
способленцу для каждого символа, который появляется в различных кон- текстах в структуре документа. Каждое вхождение данного объекта-символа ссылается на один и тот же экземпляр в совместно используемом пуле объ- ектов-приспособленцев.
234
Глава 4. Структурные паттерны
Cтрока
Cтрока
Cтрока
Колонка a b c d e f g h i j k l m n o p q r s t u v w x y z
Пул приспособленцев
Ниже изображена структура класса для этих объектов.
Glyph
— это абстракт- ный класс для представления графических объектов (некоторые из них могут быть приспособленцами). Операции, которые могут зависеть от внешнего состояния, передают его в качестве параметра. Например, операциям
Draw
(ри- сование) и
Intersects
(пересечение) должно быть известно, в каком контексте встречается глиф, иначе они не смогут выполнить то, что от них требуется.
Draw(Context)
Intersects(Point, Context)
Glyph
Draw(Context)
Intersects(Point, Context)
Row
Draw(Context)
Intersects(Point, Context)
Column
Draw(Context)
Intersects(Point, Context)
Character
char c children children
Паттерн Flyweight (приспособленец)
235Приспособленец, представляющий букву «a», содержит только соответству- ющий ей код; ни положение, ни шрифт буквы ему хранить не надо. Клиенты передают приспособленцу всю зависящую от контекста информацию, кото- рая нужна, чтобы он мог изобразить себя. Например, глифу
Row известно, где его потомки должны себя вывести, чтобы быть выстроенными в ряд по горизонтали. Поэтому вместе с запросом на рисование он может передавать каждому потомку координаты.
Поскольку число различных объектов-символов гораздо меньше числа сим- волов в документе, то и общее количество объектов существенно меньше, чем было бы при простой реализации. Документ, в котором все символы изображаются одним шрифтом и цветом, создаст порядка 100 объектов- символов (это примерно равно числу кодов в таблице ASCII) независимо от своего размера. А поскольку в большинстве документов применяется не более десятка различных комбинаций шрифта и цвета, то на практике эта величина возрастет несущественно, поэтому абстракция объекта становится применимой и к отдельным символам.
Применимость
Эффективность паттерна приспособленец во многом зависит от того, как и где он используется. Применяйте этот паттерн, когда выполнены
все ни- жеперечисленные условия:
в приложении используется большое число объектов;
из-за этого затраты на хранение высоки;
большую часть состояния объектов можно вынести вовне;
многие группы объектов можно заменить относительно небольшим ко-
личеством совместно используемых объектов, поскольку внешнее со- стояние вынесено;
приложение не зависит от идентичности объекта. Поскольку объекты- приспособленцы могут использоваться совместно, то проверка на иден- тичность возвратит признак истинности для концептуально различных объектов.
236
Глава 4. Структурные паттерны
Структура
GetFlyweight(key)
FlyweightFactory
Operation(extrinsicState)
allState
UnsharedConcreteFlyweight
Operation(extrinsicState)
intrinsicState
ConcreteFlyweight
Operation(extrinsicState)
Flyweight
if (flyweight[key] существует) {
Вернуть существующего приспособленца;
} else {
Создать нового приспособленца;
Добавить в пул приспособленцев;
Вернуть нового приспособленца;
}
Client
flyweights
На следующей схеме показано, как организуется совместное использование приспособленцев.
aClient
Пул приспособленцев
aClient
aConcreteFlyweight
intrinsicState
aConcreteFlyweight
intrinsicState
aFlyweightFactory
flyweights
Участники
Flyweight (
Glyph
) — приспособленец:
• объявляет интерфейс, с помощью которого приспособленцы могут получать внешнее состояние или как-то воздействовать на него;
ConcreteFlyweight (
Character
) — конкретный приспособленец:
• реализует интерфейс класса
Flyweight и добавляет при необходимости внутреннее состояние. Объект класса
ConcreteFlyweight должен быть
Паттерн Flyweight (приспособленец)
237совместно используемым. Любое сохраняемое им состояние должно быть внутренним, то есть не зависящим от контекста;
UnsharedConcreteFlyweight (
Row
,
Column
) — конкретный приспособле- нец, не используемый совместно:
• не все подклассы
Flyweight обязательно должны быть совместно ис- пользуемыми. Интерфейс
Flyweight допускает совместное использова- ние, но не навязывает его. Часто у объектов
UnsharedConcreteFlyweight на некотором уровне структуры приспособленца есть потомки в виде объектов класса
ConcreteFlyweight
, как, например, у объектов классов
Row и
Column
;
FlyweightFactory — фабрика приспособленцев:
• создает объекты-приспособленцы и управляет ими;
• обеспечивает совместное использование приспособленцев. Когда клиент запрашивает приспособленца, объект
FlyweightFactory предо- ставляет
существующий экземпляр или создает новый, если готового еще нет;
Client — клиент:
• хранит ссылки на одного или нескольких приспособленцев;
• вычисляет или хранит внешнее состояние приспособленцев.
Отношения
Состояние, необходимое приспособленцу для нормальной работы, клас- сифицируется на внутреннее или внешнее. Внутреннее состояние хра- нится в самом объекте
ConcreteFlyweight
. Внешнее состояние хранится или вычисляется клиентами. Клиент передает его приспособленцу при вызове операций;
клиенты не должны создавать экземпляры класса
ConcreteFlyweight напрямую, а могут получать их только от объекта
FlyweightFactory
. Это позволит гарантировать корректное совместное использование.
Результаты
При использовании приспособленцев возможны затраты на передачу, поиск или вычисление внутреннего состояния на стадии выполнения, особенно если раньше оно хранилось как внутреннее. Однако такие затраты с лихвой компенсируются экономией памяти за счет совместного использования объектов-приспособленцев.
238 Глава 4. Структурные паттерны
Экономия памяти обусловлена несколькими причинами:
уменьшение общего числа экземпляров;
сокращение объема памяти, необходимого для хранения внутреннего состояния;
вычисление, а не хранение внешнего состояния (если это действительно так).
Чем выше степень совместного использования приспособленцев, тем суще- ственнее экономия. С увеличением объема совместного состояния эконо- мия также возрастает. Самого большого эффекта удается добиться, когда суммарный объем внутренней и внешней информации о состоянии велик, а внешнее состояние вычисляется, а не хранится. Тогда совместное исполь- зование уменьшает стоимость хранения внутреннего состояния, а за счет вычислений сокращается память, отводимая под внешнее состояние.
Паттерн приспособленец часто применяется вместе с компоновщиком для представления иерархической структуры в виде графа с совместно исполь- зуемыми листовыми узлами. Из-за разделения указатель на родителя не
может храниться в листовом узле-приспособленце, а должен передаваться ему как часть внешнего состояния. Это оказывает заметное влияние на спо- соб взаимодействия объектов иерархии между собой.
Реализация
При реализации приспособленца следует учитывать следующие аспекты:
вынесение внешнего состояния. Применимость паттерна в значительной степени зависит от того, насколько легко идентифицировать внешнее состояние и вынести его за пределы совместно используемых объектов.
Вынесение внешнего состояния не уменьшает затрат на хранение, если различных внешних состояний так же много, как и объектов до совмест- ного использования. Лучший вариант — вычисление внешнего состоя- ния по объектам с другой структурой, требующей значительно меньшей памяти.
Например, в нашем редакторе документов можно поместить карту с ти- пографской информацией в отдельную структуру, а не хранить шрифт и начертание вместе с каждым символом. В этой карте будут храниться непрерывные серии символов с одинаковыми типографскими атрибу- тами. Когда объект-символ изображает себя, он получает типографские атрибуты от алгоритма обхода. Поскольку обычно в документах использу- ется немного разных шрифтов и начертаний, то хранить эту информацию
Паттерн Flyweight (приспособленец)
239отдельно от объекта-символа гораздо эффективнее, чем непосредственно в нем;
управление совместно используемыми объектами. Так как объекты ис- пользуются совместно, клиенты не должны создавать экземпляры напря- мую. Фабрика
FlyweightFactory позволяет клиентам найти подходящего приспособленца. В объектах этого класса часто присутствует ассоциатив- ное хранилище, с помощью которого можно быстро находить приспособ- ленца, нужного клиенту. Так, в примере редактора документов фабрика приспособленцев может содержать внутри себя таблицу, индексирован- ную кодом символа, и возвращать нужного приспособленца по его коду.
А если требуемый приспособленец отсутствует, он тут же создается.
Совместное использование также подразумевает некоторую форму под- счета ссылок или уборки мусора для освобождения занимаемой приспо- собленцем памяти, когда необходимость в нем отпадает. Однако ни то, ни
другое необязательно, если число приспособленцев фиксировано и не- велико (например, если речь идет о представлении набора символов кода
ASCII). В таком случае имеет смысл хранить приспособленцев постоянно.
Пример кода
Возвращаясь к примеру с редактором документов, определим базовый класс
Glyph для графических объектов-приспособленцев. На логическом уровне глифы — это составные объекты, которые обладают графическими атрибу- тами и умеют изображать себя (см. описание паттерна компоновщик (196)).
Сейчас мы ограничимся только шрифтом, но тот же подход применим и к любым другим графическим атрибутам:
class Glyph {
public:
virtual Glyph();
virtual void Draw(Window*, GlyphContext&);
virtual void SetFont(Font*, GlyphContext&);
virtual Font* GetFont(GlyphContext&);
virtual void First(GlyphContext&);
virtual void Next(GlyphContext&);
virtual bool IsDone(GlyphContext&);
virtual Glyph* Current(GlyphContext&);
virtual void Insert(Glyph*, GlyphContext&);
virtual void Remove(GlyphContext&);
protected:
Glyph();
};