Система Orphus

 

Поиск по сайту

 

Паттерны » Структурные паттерны » Flyweight

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

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

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

Motif–библиотека для разработки приложений c графическим интерфейсом под X Window System. Появилась в конце 1980-х и на данный момент считается устаревшей.

В Motif элементы графического интерфейса (кнопки, полосы прокрутки, меню и т.д.) строятся на основе виджетов. Каждый виджет имеет свое окно. Исторически окна считаются "тяжеловесными" объектами. Если приложение с графическим интерфейсом использует множество виджетов, то производительность системы может упасть. Для решения этой проблемы в Motif предусмотрены гаджеты, являющиеся безоконными аналогами виджетов. Гаджеты управляются менеджерами виджетов, использующих схему "parent-children".

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

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

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

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

Замена Motif-виджетов "легковесными" гаджетами иллюстрирует этот подход. Если виджет являются достаточно самостоятельным элементом, то гаджет находится в зависимости от своего родительского менеджера компоновки виджетов. Каждый менеджер предоставляет своим гаджетам контекстно-зависимую информацию по обработке событий, ресурсам. Гаджет хранит в себе только контекстно-независимые данные.

Структура паттерна Flyweight

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

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

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

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

Структура паттерна Flyweight. Пример

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

Паттерн 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/