Приспособленец (Flyweight)
Назначение
Уменьшает количество объектов системы с многочисленными низкоуровневыми особенностями путем совместного использования подобных объектов.
Описание
В некоторых приложениях имеется множество небольших объектов, и задача экономии памяти выходит на первый план. Паттерн Flyweight предназначен для уменьшения количества объектов в системе за счет их совместного использования. Группа объектов может быть объединена в один объект если какая-то часть состояния у них совпадает. Несовпадающую же часть (так называемый внешний контекст) предлагается передавать в качестве параметра операции.
Использование
Паттерн Flyweight можно использовать в случаях когда:
- в приложении имеется множество почти одинаковых объектов
- различающиеся части почти одинаковых объектов можно отделить от одинаковых и заменить эти почти одинаковые объекты одним совместно используемым объектом, передавая различающуюся часть как параметр операции
Реализация
Диаграмма классов
Участники
- 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(); // меняем контекст - позицию символа
}
}
Достоинства и недостатки
- Уменьшение количества обрабатываемых объектов
- При вычислении всякий раз контекстной информации теряется производительность