Паттерны » Структурные паттерны » Flyweight
Паттерн Flyweight (приспособленец)
Назначение паттерна Flyweight
- Паттерн Flyweight использует разделение для эффективной поддержки большого числа мелких объектов.
- Является стратегией Motif GUI для замены "тяжеловесных" виджетов "легковесными" гаджетами.
Motif–библиотека для разработки приложений c графическим интерфейсом под X Window System. Появилась в конце 1980-х и на данный момент считается устаревшей.
В Motif элементы графического интерфейса (кнопки, полосы прокрутки, меню и т.д.) строятся на основе виджетов. Каждый виджет имеет свое окно. Исторически окна считаются "тяжеловесными" объектами. Если приложение с графическим интерфейсом использует множество виджетов, то производительность системы может упасть. Для решения этой проблемы в Motif предусмотрены гаджеты, являющиеся безоконными аналогами виджетов. Гаджеты управляются менеджерами виджетов, использующих схему "parent-children".
Решаемая проблема
Проектирование системы из объектов самого низкого уровня обеспечивает оптимальную гибкость, но может быть неприемлемо "дорогим" решением с точки зрения производительности и расхода памяти.
Обсуждение паттерна Flyweight
Паттерн Flyweight описывает, как совместно разделять очень мелкие объекты без чрезмерно высоких издержек. Каждый объект-приспособленец имеет две части: внутреннее и внешнее состояния. Внутреннее состояние хранится (разделяется) в приспособленце и состоит из информации, не зависящей от его контекста. Внешнее состояние хранится или вычисляется объектами-клиентами и передается приспособленцу при вызове его методов.
Замена Motif-виджетов "легковесными" гаджетами иллюстрирует этот подход. Если виджет являются достаточно самостоятельным элементом, то гаджет находится в зависимости от своего родительского менеджера компоновки виджетов. Каждый менеджер предоставляет своим гаджетам контекстно-зависимую информацию по обработке событий, ресурсам. Гаджет хранит в себе только контекстно-независимые данные.
Структура паттерна Flyweight
Клиенты не создают приспособленцев напрямую, а запрашивают их у фабрики. Любые атрибуты (члены данных класса), которые не могут разделяться, являются внешним состоянием. Внешнее состояние передается приспособленцу при вызове его методов. При этом наибольшая экономия памяти достигается в том случае, если внешнее состояние не хранится, а вычисляется при вызове.
UML-диаграмма классов паттерна Flyweight
Классы, описывающие различных насекомых Ant, Locust и Cockroach могут быть "легковесными", потому что специфичная для экземпляров информация может быть вынесена наружу и затем, передаваться клиентом в запросе.
Пример паттерна Flyweight
Паттерн Flyweight использует разделение для эффективной поддержки большого числа мелких объектов. Телефонная сеть общего пользования ТФОП является примером Flyweight. Такие ресурсы как генераторы тональных сигналов (Занято, КПВ и т.д.), приемники цифр номера абонента, набираемого в тоновом наборе, являются общими для всех абонентов. Когда абонент поднимает трубку, чтобы позвонить, ему предоставляется доступ ко всем нужным разделяемым ресурсам.
Использование паттерна Flyweight
- Убедитесь, что существует проблема повышенных накладных расходов.
- Разделите состояние целевого класса на разделяемое (внутреннее) и неразделяемое (внешнее).
- Удалите из атрибутов (членов данных) класса неразделяемое состояние и добавьте его в список аргументов, передаваемых методам.
- Создайте фабрику, которая может кэшировать и повторно использовать существующие экземпляры класса.
- Для создания новых объектов клиент использует эту фабрику вместо оператора new.
- Клиент (или третья сторона) должен находить или вычислять неразделяемое состояние и передавать его методам класса.
Особенности паттерна Flyweight
- Если Flyweight показывает, как сделать множество небольших объектов, то Facade показывает, как представить целую подсистему одним объектом.
- Flyweight часто используется совместно с Composite для реализации иерархической структуры в виде графа с разделяемыми листовыми вершинами.
- Терминальные символы абстрактного синтаксического дерева Interpreter могут разделяться при помощи Flyweight.
- Flyweight объясняет, когда и как могут разделяться объекты State.
Реализация паттерна Flyweight
Паттерн Flyweight: до и после
При использовании объектов на очень низких уровнях детализации накладные расходы могут быть непомерно большими. Использование паттерна Flyweight предполагает удаление неразделяемого состояния из класса и его передачу клиентом в вызываемые методы. И хотя это накладывает большую ответственность на клиента, но теперь создается значительно меньшее число объектов-приспособленцев. Совместное использование этих экземпляров облегчается с помощью класса-фабрики, поддерживающей "кэш" существующих приспособленцев.
В этом примере, "X" состояние рассматривается как разделяемое, а "Y" состояние выносится наружу (передается клиентом при вызове метода report()
).
До
class Gazillion { public: Gazillion() { m_value_one = s_num / Y; m_value_two = s_num % Y; ++s_num; } void report() { cout << m_value_one << m_value_two << ' '; } static int X, Y; private: int m_value_one; int m_value_two; static int s_num; }; int Gazillion::X = 6, Gazillion::Y = 10, Gazillion::s_num = 0; int main() { Gazillion matrix[Gazillion::X][Gazillion::Y]; for (int i = 0; i < Gazillion::X; ++i) { for (int j = 0; j < Gazillion::Y; ++j) matrix[i][j].report(); cout << '\n'; } }
Вывод программы:
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
После
class Gazillion { public: Gazillion(int value_one) { m_value_one = value_one; cout << "ctor: " << m_value_one << '\n'; } ~Gazillion() { cout << m_value_one << ' '; } void report(int value_two) { cout << m_value_one << value_two << ' '; } private: int m_value_one; }; class Factory { public: static Gazillion *get_fly(int in) { if (!s_pool[in]) s_pool[in] = new Gazillion(in); return s_pool[in]; } static void clean_up() { cout << "dtors: "; for (int i = 0; i < X; ++i) if (s_pool[i]) delete s_pool[i]; cout << '\n'; } static int X, Y; private: static Gazillion *s_pool[]; }; int Factory::X = 6, Factory::Y = 10; Gazillion *Factory::s_pool[] = { 0, 0, 0, 0, 0, 0 }; int main() { for (int i = 0; i < Factory::X; ++i) { for (int j = 0; j < Factory::Y; ++j) Factory::get_fly(i)->report(j); cout << '\n'; } Factory::clean_up(); }
Вывод программы:
ctor: 0 00 01 02 03 04 05 06 07 08 09 ctor: 1 10 11 12 13 14 15 16 17 18 19 ctor: 2 20 21 22 23 24 25 26 27 28 29 ctor: 3 30 31 32 33 34 35 36 37 38 39 ctor: 4 40 41 42 43 44 45 46 47 48 49 ctor: 5 50 51 52 53 54 55 56 57 58 59 dtors: 0 1 2 3 4 5
Паттерн Flyweight: разделение иконок.
Паттерн Flyweight показывает, как эффективно разделять множество мелких объектов. Ключевая концепция - различие между внутренним и внешним состояниями.
Внутреннее состояние состоит из информации, которая не зависит от контекста и может разделяться (например, имя иконки, ее ширина и высота). Оно хранится в приспособленце (то есть в классе Icon
).
Внешнее состояние не может разделяться, оно зависит от контекста и изменяется вместе с ним (например, координаты верхнего левого угла для каждого экземпляра иконки). Внешнее состояние хранится или вычисляется клиентом и передается приспособленцу при вызове операций. Клиенты не должны создавать экземпляры приспособленцев напрямую, а получать их исключительно из объекта FlyweightFactory
для правильного разделения.
#include <iostream.h> #include <string.h> 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; _height = 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 *getIcon(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++]; } static void reportTheIcons() { cout << "Active Flyweights: "; for (int i = 0; i < _numIcons; i++) cout << _icons[i]->getName() << " "; cout << endl; } private: enum { MAX_ICONS = 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[1] = 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[1] = 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")); dialogs[1] = 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
Источник: http://sourcemaking.com/design_patterns/flyweight/