Меню
Главная
Авторизация/Регистрация
 
Главная arrow Информатика arrow Введение в архитектуру программного обеспечения

Паттерн Приспособленец (Flyweight)

Название и классификация паттерна

Приспособленец — паттерн, структурирующий объекты.

Назначение паттерна Flyweight

Паттерн Flyweight использует разделение для эффективной поддержки большого числа мелких объектов.

Решаемая проблема

Проектирование системы из объектов самого низкого уровня обеспечивает оптимальную гибкость, но может быть неприемлемо «дорогим» решением с точки зрения производительности и расхода памяти.

Обсуждение паттерна Flyweight

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

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

Иерархия представления символов

Рис. 54. Иерархия представления символов

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

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

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

Приспособленцы моделируют концепции или сущности, число которых слишком велико для представления объектами. Например, редактор документов мог бы создать по одному Приспособленцу для каждой буквы алфавита. Каждый Приспособленец хранит код символа, но координаты положения символа в документе и стиль его начертания определяются алгоритмами размещения текста и командами форматирования, действующими в том месте, где символ появляется. Код символа — это внутреннее состояние, а все остальное — внешнее. Логически для каждого вхождения данного символа в документ существует объект.

Физически, однако, есть лишь по одному объекту-Приспособленцу для каждого символа, который появляется в различных контекстах в структуре документа. Каждое вхождение данного объекта-символа ссылается на один и тот же экземпляр в разделяемом пуле объек-тов-Приспособленцев.

На рис. 55 изображена структура класса для этих объектов. Glyph — это абстрактный класс для представления графических объектов (некоторые из них могут быть Приспособленцами). Операции,

Glyph

Draw(Context) lntersects(Point, Context)

Row

children -

Draw(Context) lntersects(Point, Context)

Column О-

- children

Draw(Context) lntersects(Point, Context)

Character

Draw(Context) lntersects(Point, Context)

char c

Рис. 55. Структура класса объектов-Приспособленцев

которые могут зависеть от внешнего состояния, передают его в качестве параметра. Например, операциям Draw (рисование) и Intersects (пересечение) должно быть известно, в каком контексте встречается глиф, иначе они не смогут выполнить то, что от них требуется.

Приспособленец, представляющий букву «а», содержит только соответствующий ей код; ни положение, ни шрифт буквы ему хранить не надо. Клиенты передают Приспособленцу всю зависящую от контекста информацию, которая нужна, чтобы он мог изобразить себя. Например, глифу Row известно, где его потомки должны себя показать, чтобы это выглядело как горизонтальная строка. Поэтому вместе с запросом на рисование он может передавать каждому потомку координаты.

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

Применимость

Эффективность паттерна Приспособленец во многом зависит от того, как и где он используется. Паттерн рекомендуется применять, когда выполнены все нижеперечисленные условия:

  • • в приложении используется большое число объектов, из-за чего накладные расходы на хранение высоки;
  • • многие группы объектов можно заменить относительно небольшим количеством разделяемых объектов, поскольку внешнее состояние вынесено;
  • • приложение не зависит от идентичности объекта. Поскольку объекты-Приспособленцы могут разделяться, то проверка на идентичность возвратит «истину» для концептуально различных объектов.

Структура паттерна Приспособленец (Flyweight)

Клиенты не создают Приспособленцев напрямую, а запрашивают их у фабрики. Любые атрибуты (члены данных класса), которые не

ConcreteFly weight

Operation(extrinsicState)

}

Client

FlyweightFactory

GetFlyweig(key)

9

?i

1

о

if (flyewig ht[key] существует) { Вернуть существующего приспособленца;

} else {

Создать нового приспособленца; Добавить в пул приспособленцев; Вернуть нового приспособленца;

flyweights

*

Flyweight

Operation(extrinsicState)

intrinsicState

? UnsharedConcreteFlyweight

Operation(extrinsicState) allState

Рис. 56. UML-диаграмма классов паттерна Приспособленец

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

Участники

Flyweight (Glyph) — Приспособленец: объявляет интерфейс, с помощью которого Приспособленцы могут получать внешнее состояние или как-то воздействовать на него.

ConcreteFlyweight (Character) — конкретный Приспособленец: реализует интерфейс класса Flyweight и добавляет при необходимости внутреннее состояние. Объект класса ConcreteFlyweight должен быть разделяемым. Любое сохраняемое им состояние должно быть внутренним, т. е. не зависящим от контекста.

UnsharedConcreteFlyweight (Row, Column) — неразделяемый конкретный Приспособленец: не все подклассы Flyweight обязательно должны быть разделяемыми. Интерфейс Flyweight допускает разделение, но не навязывает его. Часто у объектов UnsharedConcreteFlyweight на некотором уровне структуры Приспособленца есть потомки в виде объектов класса Concret eFlyweight, как, например, у объектов классов Row и Column.

FlyweightFactory — фабрика Приспособленцев: создает объек-ты-Приспособленцы и управляет ими; обеспечивает должное разделение Приспособленцев. Когда клиент запрашивает Приспособленца, объект FlyweightFactory предоставляет существующий экземпляр или создает новый, если готового еще нет.

Client — клиент: хранит ссылки на одного или нескольких Приспособленцев; вычисляет или хранит внешнее состояние Приспособленцев.

Отношения

Состояние, необходимое Приспособленцу для нормальной работы, можно охарактеризовать как внутреннее или внешнее. Первое хранится в самом объекте ConcreteFlyweight. Внешнее состояние хранится или вычисляется клиентами. Клиент передает его Приспособленцу при вызове операций.

Клиенты не должны создавать экземпляры класса Concrete-Flyweight напрямую, а могут получать их только от объекта Flyweight-Factory. Это позволит гарантировать корректное разделение.

Результаты

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

Экономия памяти возникает по ряду причин:

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

Чем выше степень разделения Приспособленцев, тем существеннее экономия. С увеличением объема разделяемого состояния экономия также возрастает. Самого большого эффекта удается добиться, когда суммарный объем внутренней и внешней информации о состоянии велик, а внешнее состояние вычисляется, а не хранится. Тогда разделение уменьшает стоимость хранения внутреннего состояния, а за счет вычислений сокращается память, отводимая под внешнее состояние.

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

UML-диаграмма классов паттерна Flyweight

Структура паттерна Приспособленец показана на рис. 57.

Классы, описывающие различных насекомых Ant (муравей), Locust (саранча) и Cockroach (таракан), могут быть «легковесными», потому что специфичная для экземпляров информация может быть вынесена наружу и затем передаваться клиентом в запросе (рис. 58).

UML-диаграмма паттерна Приспособленец

Рис. 57. UML-диаграмма паттерна Приспособленец

иМЬ-диаграмма, описывающая классы насекомых при использовании

Рис. 58. иМЬ-диаграмма, описывающая классы насекомых при использовании

паттерна Приспособленец

Пример паттерна Flyweight

Паттерн Flyweight использует разделение для эффективной поддержки большого числа мелких объектов. Телефонная сеть общего пользования ТФОП является примером Flyweight. Такие ресурсы, как генераторы тональных сигналов (Занято, КПВ и т. д.), приемники цифр номера абонента, набираемого в тоновом наборе, являются общими для всех абонентов. Когда абонент поднимает трубку, чтобы позвонить, ему предоставляется доступ ко всем нужным разделяемым ресурсам.

Использование паттерна Flyweight

Убедитесь, что существует проблема повышенных накладных расходов.

Разделите состояние целевого класса на разделяемое (внутреннее) и неразделяемое (внешнее).

Удалите из атрибутов (членов данных) класса неразделяемое состояние и добавьте его в список аргументов, передаваемых методам.

Создайте фабрику, которая может кэшировать и повторно использовать существующие экземпляры класса.

Для создания новых объектов клиент использует эту фабрику вместо оператора new.

Клиент (или третья сторона) должен находить или вычислять неразделяемое состояние и передавать его методам класса.

Особенности паттерна Flyweight

Если Flyweight показывает, как сделать множество небольших объектов, то Facade показывает, как представить целую подсистему одним объектом.

Flyweight часто используется совместно с Composite для реализации иерархической структуры в виде графа с разделяемыми листовыми вершинами.

Терминальные символы абстрактного синтаксического дерева Interpreter могут разделяться при помощи Flyweight.

Flyweight объясняет, когда и как могут разделяться объекты State.

Реализация паттерна Flyweight

Паттерн Flyweight показывает, как эффективно разделять множество мелких объектов. Ключевая концепция — различие между внутренним и внешним состояниями. Внутреннее состояние состоит из информации, которая не зависит от контекста и может разделяться (например, имя иконки, ее ширина и высота). Оно хранится в Приспособленце (т. е. в классе Icon). Внешнее состояние не может разделяться, оно зависит от контекста и изменяется вместе с ним (например, координаты верхнего левого угла для каждого экземпляра иконки). Внешнее состояние хранится или вычисляется клиентом и передается Приспособленцу при вызове операций. Клиенты не должны создавать экземпляры Приспособленцев напрямую, а получают их исключительно из объекта FlyweightFactory для правильного разделения.

#include

#include

class Icon

{

public:

Icon(char *fileName)

{

strcpy(_name, fileName); if (!strcmp(fileName, "go"))

{

_width = 20;

_height = 20;

}

if (!strcmp(fileName, "stop"))

{

_width = 40;

_height = 40;

}

if (!strcmp(fileName, "select"))

{

_width = 60;

Jieight = 60;

}

if (!strcmp(fileName, "undo"))

{

_width = 30;

_height = 30;

}

}

const char *getName()

{

return _name;

}

draw(int x, int y)

{

cout << " drawing "<< _name << upper left (" << x << << y

<< ") - lower right (" << x + _width << << y + _height << ")" << endl;

}

private:

char_name[20]; int _width; int height;

class FlyweightFactory

{

public:

static Icon *get!con(char *name)

{

for (int i = 0; i < _numIcons; i++) if (!strcmp(name, _icons[i]->getName())) return _icons[i];

_icons[_numIcons] = new Icon(name); return _icons[_numIcons++1;

}

static void reportTheIcons()

{

cout << "Active Flyweights: for (int i = 0; i < _numIcons; i++) cout << _icons[i]->getName() << " cout << endl;

}

private:

enum

{

MAXICONS = 5

};

static int _numIcons;

static Icon *_icons[MAX_ICONS];

int FlyweightFactory::_numIcons = 0;

Icon *FlyweightFactory::_icons[|;

class DialogBox

{

public:

DialogBox(int x, int y, int incr):

_iconsOriginX(x), _iconsOriginY(y), _iconsXIncrement(incr){} virtual void draw() = 0; protected:

Icon *_icons[3]; int _iconsOriginX; int _iconsOriginY; int _iconsXIncrement;

class FileSelection: public DialogBox

{

public:

FileSelection(Icon *first, Icon *second, Icon *third): DialogBox(100, 100, 100)

{

_icons[0] = first;

_icons[l] = second;

_icons[2] = third;

}

void draw()

{

cout << "drawing FileSelection:" << endl; for (int i = 0; i < 3; i++)

_icons[i] ->draw(_iconsOriginX +

(i *_iconsXIncrement), _iconsOriginY);

}

};

class CommitTransaction: public DialogBox

{

public:

CommitTransaction(Icon *first, Icon *second, Icon *third): DialogBox(150, 150, 150)

{

_icons[0] = first;

_icons[l] = second;

_icons[2] = third;

void draw()

{

cout << "drawing CommitTransaction:" << endl; for (int i = 0; i < 3; i++)

_icons[i] ->draw(_iconsOriginX +

(i *_iconsXIncrement), _iconsOriginY);

}

};

int main()

{

DialogBox *dialogs[2]; dialogs[0] = new FileSelection(

FlyweightFactory::getIcon("go"), FlyweightFactory::getIcon("stop"), FlyweightFactory::getIcon("select")); dialogsfl] = new CommitTransaction(

FlyweightFactory::getIcon("select"),

FlyweightFactory::getIcon("stop"),

FlyweightFactory::getIcon("undo"));

for (int i = 0; i < 2; i++) dialogs[i]->draw();

FlyweightFactory::reportTheIcons();

}

Вывод программы:

drawing FileSelection:

drawing go: upper left (100,100) - lower right (120,120) drawing stop: upper left (200,100) - lower right (240,140) drawing select: upper left (300,100) - lower right (360,160) drawing CommitTransaction: drawing select: upper left (150,150) - lower right (210,210) drawing stop: upper left (300,150) - lower right (340,190) drawing undo: upper left (450,150) - lower right (480,180)

Active Flyweights: go stop select undo

Родственные паттерны

Паттерн Приспособленец часто используется в сочетании с Компоновщиком для реализации иерархической структуры в виде ациклического направленного графа с разделяемыми листовыми вершинами. Часто наилучшим способом реализации объектов состояния и стратегии является паттерн Приспособленец.

 
Если Вы заметили ошибку в тексте выделите слово и нажмите Shift + Enter
< Пред   СОДЕРЖАНИЕ   След >
 

Популярные страницы