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

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск

К основной странице курса

Назначение

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

Описание

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

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

Паттерн Flyweight можно использовать в случаях когда:

  • в приложении имеется множество почти одинаковых объектов
  • различающиеся части почти одинаковых объектов можно отделить от одинаковых и заменить эти почти одинаковые объекты одним совместно используемым объектом, передавая различающуюся часть как параметр операции

Реализация

Диаграмма классов

FlyweightCommon.png

Участники

  • Flyweight - приспособленец

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

  • ConcreteFlyweight - конкретный приспособленец

Реализует интерфейс Приспособленца и добавляет при необходимости внутреннее состояние. Объект ConcreteFlyweight должен быть разделяемым

  • FlyweightFactory - фабрика приспособленцев

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

  • Context - контекст

Внешний контекст для всех приспособленцев. По нему приспособленцы вычисляют своё внешнее состояние

  • Client - клиент

Хранит ссылки на приспособленцев. Хранит контекст - внешнее состояние приспособленцев.

Пример

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

В данном примере в контексте хранится текущая позиция рисования, которая после каждого рисования символа увеличивается на 1. Кроме того, в контексте хранится набор интервалов. Если символ попадает в один из интервалов, то он рисуется жирным.

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

Приспособленцем здесь выступает символ, который приспосабливается к ситуации в зависимости от контекста.

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

Код примера

class MainApp
{
    static void Main()
    {
        string Str = "AAZZBBZB";

        CharacterFactory factory = new CharacterFactory();

        Context context = new Context();
        context.AddInterval(2, 4);
        context.AddInterval(6, 7);

        foreach (char c in Str)
        {
            Character character = factory.GetCharacter(c); // получить приспособленца
            character.Draw(context);
        }
    }
}

class Pair 
{
    public int Left,Right;
    public Pair(int left, int right)
    {
        Left = left;
        Right = right;
    }
}

class Context
{
    private int pos = 1;
    private List<Pair> list = new List<Pair>(); 
    public void AddInterval(int left, int right)
    {
        list.Add(new Pair(left,right));
    }
    public void Next()
    {
        pos++;
    }
    public bool InInterval()
    {
        foreach (var p in list)
        {
            if (pos >= p.Left && pos <= p.Right)
                return true;
        }
        return false;
    }
}

class CharacterFactory
{
    private Dictionary<char, Character> characters = new Dictionary<char, Character>();

    public Character GetCharacter(char key)
    {
        Character character = null;
        if (characters.ContainsKey(key)) // если есть такой приспособленец
        {
            character = characters[key]; // то просто вернуть его
        }
        else
        {
            character = new Character(key);
            characters.Add(key, character); // иначе создать, добавить к словарю приспособленцев и вернуть
        }
        return character;
    }
}

class Character
{
    private char symbol;
    public Character(char sym)
    {
        symbol = sym;
    }
    public void Draw(Context context)
    {
        if (context.InInterval())
            Console.WriteLine("Symbol = {0} BOLD", symbol); // попавшие в один из интервалов контекста символы рисуются жирным
        else Console.WriteLine("Symbol = {0}", symbol);
        context.Next(); // меняем контекст - позицию символа
    }
}

Достоинства и недостатки

  • Уменьшение количества обрабатываемых объектов
  • При вычислении всякий раз контекстной информации теряется производительность