Паттерн проектирования «Приспособленец» (Flyweight)
В случае использования структурного шаблона проектирования «Приспособленец», объект, который представляет себя в разных местах программы как уникальный экземпляр, по факту таковым не является.
В качестве примера можно привести заказ молочного коктейля в кафе. Представьте, что вы отстояли большую очередь жарким летом и, наконец-то, добрались до прилавка, где заказали стакан коктейля. Продавец, получив очередной заказ, готовит не один стакан, а сразу большую емкость, взбивая коктейль на специальном оборудовании. Для чего это делается, я думаю, понятно — сразу готовится коктейль и кому-нибудь еще, кто стоит в очереди за вами. Таким образом экономится не только время, но и ресурсы, например, электричество. Так вот, электрические ресурсы, которые делятся (sharing), в нашем случае и являются приспособленцами.
Аналогичный пример из жизни — покупка чая в уличном ларьке на массовом мероприятии (сразу заваривается большой объем чая, что избавляет от необходимости постоянно заваривать отдельный стаканчик для каждого покупателя).
Если вернуться к программированию, то можно сказать, что «Приспособленец» применяется в целях минимизации использования памяти/вычислительной стоимости посредством разделения ресурсов с максимальным числом схожих объектов.
Давайте рассмотрим, как это выглядит в коде. Возьмем за основу последний пример с чаем. Итак, в самом начале у нас существуют различные виды — это Tea и TeaMaker:
Кроме того, существует TeaShop, где заказы принимаются и обрабатываются:
Ну и непосредственный пример использования:
Когда следует использовать паттерн приспособленец
Паттерн Приспособленец (Flyweight) — структурный шаблон проектирования, который позволяет использовать разделяемые объекты сразу в нескольких контекстах. Данный паттерн используется преимущественно для оптимизации работы с памятью.
В качестве стандартного применения данного паттерна можно привести следующий пример. Текст состоит из отдельных символов. Каждый символ может встречаться на одной странице текста много раз. Однако в компьютерной программе было бы слишком накладно выделять память для каждого отдельного символа в тексте. Гораздо проще было бы определить полный набор символов, например, в виде таблицы из 128 знаков (алфавитно-цифровые символы в разных регистрах, знаки препинания и т.д.). А в тексте применить этот набор общих разделяемых символов, вместо сотен и тысяч объектов, которые могли бы использоваться в тексте. И как следствие подобного подхода будет уменьшение количества используемых объектов и уменьшение используемой памяти.
Паттерн Приспособленец следует применять при соблюдении всех следующих условий:
- Когда приложение использует большое количество однообразных объектов, из-за чего происходит выделение большого количества памяти
- Когда часть состояния объекта, которое является изменяемым, можно вынести во вне. Вынесение внешнего состояния позволяет заменить множество объектов небольшой группой общих разделяемых объектов.
Ключевым моментом здесь является разделение состояния на внутренне и внешнее. Внутреннее состояние не зависит от контекста. В примере с символами внутреннее состояние описывается кодом символа из таблицы кодировки. Так как внутреннее состояние не зависит от контекста, то оно может быть разделяемым и поэтому выносится в разделяемые объекты.
Внешнее состояние зависит от контекста, является изменчивым. В применении к символам внешнее состояние может представлять положение символа на странице. То есть код символа может быть использован многими символами, тогда как положение на странице будет для каждого символа индивидуально.
При создании приспособленца внешнее состояние выносится. В приспособленце остается только внутреннее состояние. То есть в примере с символами приспособленец будет хранить код символа.
Отношения в данном паттерне можно описать следующей схемой:
Формальное определение паттерна на C#:
class FlyweightFactory < Hashtable flyweights = new Hashtable(); public FlyweightFactory() < flyweights.Add("X", new ConcreteFlyweight()); flyweights.Add("Y", new ConcreteFlyweight()); flyweights.Add("Z", new ConcreteFlyweight()); >public Flyweight GetFlyweight(string key) < if (!flyweights.ContainsKey(key)) flyweights.Add(key, new ConcreteFlyweight()); return flyweights[key] as Flyweight; >> abstract class Flyweight < public abstract void Operation(int extrinsicState); >class ConcreteFlyweight : Flyweight < int intrinsicState; public override void Operation(int extrinsicState) < >> class UnsharedConcreteFlyweight : Flyweight < int allState; public override void Operation(int extrinsicState) < allState = extrinsicState; >> class Client < void Main() < int extrinsicstate = 22; FlyweightFactory f = new FlyweightFactory(); Flyweight fx = f.GetFlyweight("X"); fx.Operation(--extrinsicstate); Flyweight fy = f.GetFlyweight("Y"); fy.Operation(--extrinsicstate); Flyweight fd = f.GetFlyweight("D"); fd.Operation(--extrinsicstate); UnsharedConcreteFlyweight uf = new UnsharedConcreteFlyweight(); uf.Operation(--extrinsicstate); >>
Участники
- Flyweight : определяет интерфейс, через который приспособленцы-разделяемые объекты могут получать внешнее состояние или воздействовать на него
- ConcreteFlyweight : конкретный класс разделяемого приспособленца. Реализует интерфейс, объявленный в типе Flyweight, и при необходимости добавляет внутреннее состояние. Причем любое сохраняемое им состояние должно быть внутренним, не зависящим от контекста
- UnsharedConcreteFlyweight : еще одна конкретная реализация интерфейса, определенного в типе Flyweight, только теперь объекты этого класса являются неразделяемыми
- FlyweightFactory : фабрика приспособленцев — создает объекты разделяемых приспособленцев. Так как приспособленцы разделяются, то клиент не должен создавать их напрямую. Все созданные объекты хранятся в пуле. В примере выше для определения пула используется объект Hashtable, но это не обязательно. Можно применять и другие классы коллекций. Однако в зависимости от сложности структуры, хранящей разделяемые объекты, особенно если у нас большое количество приспособленцев, то может увеличиваться время на поиск нужного приспособленца — наверное это один из немногих недостатков данного паттерна. Если запрошенного приспособленца не оказалось в пуле, то фабрика создает его.
- Client : использует объекты приспособленцев. Может хранить внешнее состояние и передавать его в качестве аргументов в методы приспособленцев
Рассмотрим пример. Допустим, мы проектируем программу для моделирования города. Город состоит из отдельных домов, поэтому нам надо создать объекты этих домов. Однако домов в городе может быть множество: сотни, тысячи. Они могут иметь разный вид, отличаться по различным признакам. Однако, как правило, многие дома делаются по стандартным проектам. И фактически мы можем выделить несколько типов домов, например, пятиэтажные кирпичные хрущевки, многоэтажные панельные высотки и так далее.
Используя некоторый анализ, мы можем выделить внутренне состояния домов и внешнее. К внутреннему состоянию, например, может относиться количество этажей, материал (кирпичи, панели и т.д.), или те показатели, которые определены его шаблоном, планом проектирования. К внешнему состоянию может относиться положение дома на географической карте, то есть его координаты, цвет дома, и так далее, то есть такие показатели, которые для каждого отдельного дома могут быть относительно индивидуальны.
В этом случае реализация строительства домов на C# с применением паттерна Flyweight могла бы выглядеть следующим образом:
class Program < static void Main(string[] args) < double longitude = 37.61; double latitude = 55.74; HouseFactory houseFactory = new HouseFactory(); for (int i = 0; i < 5;i++) < House panelHouse = houseFactory.GetHouse("Panel"); if (panelHouse != null) panelHouse.Build(longitude, latitude); longitude += 0.1; latitude += 0.1; >for (int i = 0; i < 5; i++) < House brickHouse = houseFactory.GetHouse("Brick"); if (brickHouse != null) brickHouse.Build(longitude, latitude); longitude += 0.1; latitude += 0.1; >Console.Read(); > > abstract class House < protected int stages; // количество этажей public abstract void Build(double longitude, double latitude); >class PanelHouse : House < public PanelHouse() < stages = 16; >public override void Build(double longitude, double latitude) < Console.WriteLine("Построен панельный дом из 16 этажей; координаты: широты и долготы", latitude, longitude); > > class BrickHouse : House < public BrickHouse() < stages = 5; >public override void Build(double longitude, double latitude) < Console.WriteLine("Построен кирпичный дом из 5 этажей; координаты: широты и долготы", latitude, longitude); > > class HouseFactory < Dictionaryhouses = new Dictionary(); public HouseFactory() < houses.Add("Panel", new PanelHouse()); houses.Add("Brick", new BrickHouse()); >public House GetHouse(string key) < if (houses.ContainsKey(key)) return houses[key]; else return null; >>
В качестве интерфейса приспособленца выступает абстрактный класс House, который определяет переменную stages — количество этажей, поскольку количество этажей относится к внутреннему состоянию, которое присуще всем домам. И также определяется метод Build() , который в качестве параметра принимает широту и долготу расположения дома — внешнее состояние.
Конкретные классы разделяемых приспособленцев — PanelHouse и BrickHouse отвечают за построение конкретных типов домов. Поскольку архитектурный план проектирования может точно задавать количество этажей для определенного типа дома, то в данном случае количество этажей устанавливается в конструкторе.
Фабрика HouseFactory создает два объекта дома для каждого конкретного приспособленца и возвращает их в методе GetHouse() в зависимости от параметра.
В роли клиента выступает класс Program, который задает начальные широту и долготу — внешнее состояние домов и использует фабрику для создания домов. Причем в реальности мы будем оперировать всего лишь двумя объектами, которые будут храниться в словаре в HouseFactory.
Когда следует использовать паттерн приспособленец
Приспособленец — паттерн, структурирующий объекты таким образом, что из них инстанцируется всего лишь ограниченный необходимый набор экземпляров вместо всего большого множества.
Условия, Задача, Назначение
Решает задачу предотвращения инстанцирования большого количества объектов, требуемого для представления всех сущностей системы.
Объектно-ориентированный дизайн систем применяется повсеместно, успешно решая возлагаемые на него задачи представления системы в виде функционально-законченных сущностей, инкапсулирующих, решающих свои конкретные задачи и реализующих свою бизнес логику. Одна из самых частых возникающих проблем здесь – огромное количество инстанцируемых экземпляров всех сущностей системы.
В рамках ООП каждая хоть какая-то законченная функциональная единица по-хорошему должна представлять собой самостоятельный объект со своим состоянием и функциями – а что, если система большая и таких единиц должно присутствовать миллионы и далее по возрастающей в зависимости от действий пользователя – тут сколько бы ни было оперативной памяти, а все равно очень легко возможен вариант, когда ее не хватит на очередную порцию каких-нибудь инстанцируемых экземпляров сущностей по запросу пользователя.
Паттерн приспособленец предлагает одно из решений данной проблемы, вполне уже известное в истории ПО, но в несколько другом исполнении – речь идет о пуле объектов.
Здесь подход пула предлагается применять следующим образом: у объектов этого огромного множества выделяется фиксированное число их состояний, которые они принимают в процессе своей деятельности, на каждое такое состояние создается по специальному объекту и далее в процессе функционирования в программе инстанцируется и фигурирует только это ограниченное количество экземпляров; когда нужен снова подобный объект, клиент выбирает из этого списка подходящий, создает на него ссылку, и когда наступает черед чтобы этот объект выполнил свои собственные задачи – клиент передает ему все другие необходимые для работы данные и запускает эти его общие разделяемые другими клиентами методы.
Мотивация
Для дальнейшего понимания рассмотрим опять же ситуацию с текстовыми редакторами. Например, в большинстве редакторов документов имеются средства форматирования и редактирования текстов, в той или иной степени модульные. Объектно-ориентированные редакторы обычно применяют объекты для представления таких встроенных элементов, как таблицы и рисунки. Но они не используют объекты для представления каждого символа, несмотря на то, что это увеличило бы гибкость на самых нижних уровнях приложения. Ведь тогда к рисованию и форматированию символов и встроенных элементов можно было бы применить единообразный подход (см. паттерн Composite). И для поддержки новых наборов символов не пришлось бы как-либо затрагивать остальные функции редактора. Да и общая структура приложения отражала бы физическую структуру документа. На следующей диаграмме показано, как редактор документов мог бы воспользоваться объектами для представления символов:
У такого дизайна есть один недостаток — стоимость. Даже в документе скромных размеров было бы несколько сотен тысяч объектов-символов, а это привело бы к расходованию огромного объема памяти и неприемлемым затратам во время выполнения. Паттерн приспособленец показывает, как разделять очень мелкие объекты без недопустимо высоких издержек.
Приспособленец — это разделяемый объект, который можно использовать одновременно в нескольких контекстах, другими словами сколько угодно много клиентов могут вызывать методы этого объекта, использовать их логику, передавая в них только лишь какие-то контекстно-зависимые данные, и каждый раз получая новый результат, который собственно и ожидает данный конкретный клиент. В каждом контексте приспособленец выглядит как независимый объект, то есть неотличим от экземпляра, который не разделяется. Приспособленцы не могут делать предположений о контексте, в котором работают.
Ключевая идея здесь — различие между внутренним и внешним состояниями.
Внутреннее состояние хранится в самом приспособленце и состоит из информации, не зависящей от его контекста. Именно поэтому он может разделяться. Внешнее состояние зависит от контекста и изменяется вместе с ним, поэтому не подлежит разделению. Объекты-клиенты отвечают за передачу внешнего состояния приспособленцу, когда в этом возникает необходимость.
Приспособленцы моделируют концепции или сущности, число которых слишком велико для представления объектами. Например, редактор документов мог бы создать по одному приспособленцу для каждой буквы алфавита. Каждый приспособленец хранит код символа, но координаты положения символа в документе и стиль его начертания определяются алгоритмами размещения текста и командами форматирования, действующими в том месте, где символ появляется. Код символа — это внутреннее состояние, а все остальное — внешнее.
Логически для каждого вхождения данного символа в документ существует объект:
Физически, однако, имеется лишь по одному объекту-приспособленцу для каждого символа, который появляется в различных местах в структуре документа. Каждое вхождение данного объекта-символа ссылается на один и тот же экземпляр в разделяемом пуле объектов-приспособленцев:
Ниже изображена структура класса для этих объектов. Glyph — это абстрактный класс для представления графических объектов (некоторые из них могут быть приспособленцами). Операции, которые могут зависеть от внешнего состояния, передают его в качестве параметра. Например, операциям Draw (рисование) и Intersects (пересечение) должно быть известно, в каком контексте (в какой родительской структуре) встречается глиф, иначе они не смогут выполнить то, что от них требуется:
Приспособленец, представляющий букву «а», содержит только соответствующий ей код: ни положение, ни шрифт буквы ему хранить не надо. Клиенты передают приспособленцу всю зависящую от контекста информацию, которая нужна, чтобы он мог изобразить себя. Например, глифу Row известно, где его потомки должны себя показать, чтобы это выглядело как горизонтальная строка. Поэтому вместе с запросом на рисование он может передавать каждому потомку координаты.
Поскольку число различных объектов-символов гораздо меньше, чем число символов в документе, то и общее количество объектов существенно меньше, чем было бы при простой реализации. Документ, в котором все символы изображаются одним шрифтом и цветом, создаст порядка 100 объектов-символов (это примерно равно числу кодов в таблице ASCII) независимо от своего размера. А поскольку в большинстве документов применяется не более десятка различных комбинаций шрифта и цвета, то на практике эта величина возрастет несущественно. Поэтому абстракция объекта становится применимой и к отдельным символам и главное – количество инстанцируемых сущностей уже не зависит от действий пользователя: какие бы документов каких объемов он не создавал – количество экземпляров букв текста будет фиксировано а память будет расходоваться только, по сути, на дополнительные ссылки на объекты.
Признаки применения, использования паттерна Приспособленец (Flyweight)
Эффективность паттерна приспособленец во многом зависит от того, как и где он используется. Применяйте этот паттерн, когда выполнены все нижеперечисленные условия:
- В приложении используется большое число объектов и из-за этого накладные расходы на хранение высоки.
- Большую часть состояния объектов можно вынести вовне (в ответственность клиентов, которые создают и используют эти объекты).
- Многие группы объектов можно заменить относительно небольшим количеством разделяемых объектов, поскольку внешнее состояние вынесено.
- Приложение не зависит от идентичности объекта. Поскольку объекты-приспособленцы могут разделяться, то проверка на идентичность возвратит истину для «концептуально» различных объектов (внутреннее состояние объектов же не меняется). Ключевой момент в том, что когда эти объекты запускают свои функции, то с учетом переданной от клиента информации (внешнего состояния) – результат выполнения получения совсем не идентичен другим.
Решение
На следующей диаграмме показано, как приспособленцы разделяются:
Участники паттерна Приспособленец (Flyweight)
- Flyweight (Glyph) – приспособленец.
Объявляет интерфейс, с помощью которого приспособленцы могут получать внешнее состояние или как-то воздействовать на него. - ConcreteFlyweight (Character) — конкретный приспособленец.
Реализует интерфейс класса Flyweight и добавляет при необходимости внутреннее состояние. Объект класса ConcreteFlyweight должен быть разделяемым. Любое сохраняемое им состояние должно быть внутренним, то есть не зависящим от контекста. - UnsharedConcreteFlyweight (Row, Column) — неразделяемый конкретный приспособленец.
Не все подклассы Flyweight обязательно должны быть разделяемыми. Интерфейс Flyweight допускает разделение, но не навязывает его. Часто у объектов UnsharedConcreteFlyweight на некотором уровне структуры приспособленца есть потомки в виде объектов класса ConcreteFlyweight, как, например, у объектов классов Row и Column. - FlyweightFactory — фабрика приспособленцев.
Создает объекты-приспособленцы и управляет ими.
Обеспечивает должное разделение приспособленцев. Когда клиент запрашивает приспособленца, объект FlyweightFactory предоставляет существующий экземпляр или создает новый, если готового еще нет. - Client – клиент.
Хранит ссылки на одного или нескольких приспособленцев.
Вычисляет или хранит внешнее состояние приспособленцев.
Схема использования паттерна Приспособленец (Flyweight)
Cостояние, необходимое приспособленцу для нормальной работы, можно охарактеризовать как внутреннее или внешнее. Первое хранится в самом объекте ConcreteFlyweight. Внешнее состояние хранится или вычисляется клиентами. Клиент передает его приспособленцу при вызове операций.
Клиенты не должны создавать экземпляры класса ConcreteFlyweight напрямую, а могут получать их только от объекта FlyweightFactory. Это позволит гарантировать корректное разделение.
Вопросы, касающиеся реализации паттерна Приспособленец (Flyweight)
При реализации приспособленца следует обратить внимание на следующие вопросы:
- Вынесение внешнего состояния.
Применимость паттерна в значительной степени зависит от того, насколько легко можно «очертить» внешнее состояние и вынести его за пределы разделяемых объектов. Вынесение внешнего состояния не уменьшает стоимости хранения, если различных внешних состояний так же много, как и объектов до разделения. Лучший вариант — внешнее состояние вычисляется по объектам с другой структурой, требующей значительно меньшей памяти.
Например, в нашем редакторе документов мы можем поместить карту с типографской информацией в отдельную структуру, а не хранить шрифт и начертание вместе с каждым символом. Данная карта будет отслеживать непрерывные серии символов с одинаковыми типографскими атрибутами. Когда объект-символ изображает себя, он получает типографские атрибуты от алгоритма обхода. Поскольку обычно в документах используется немного разных шрифтов и начертаний, то хранить эту информацию отдельно от объекта-символа гораздо эффективнее, чем непосредственно в нем. - Управление разделяемыми объектами.
Так как объекты разделяются, клиенты не должны инстанцировать их напрямую. Фабрика FlyweightFactory позволяет клиентам найти подходящего приспособленца. В объектах этого класса часто есть хранилище, организованное в виде ассоциативного массива (или хеш-таблицы), с помощью которого можно быстро находить приспособленца, нужного клиенту. Так, в примере редактора документов фабрика приспособленцев может содержать внутри себя таблицу, индексированную кодом символа, и возвращать нужного приспособленца по его коду. А если требуемый приспособленец отсутствует, он тут же создается. Разделяемость подразумевает также, что имеется некоторая форма подсчета ссылок или сбора мусора для освобождения занимаемой приспособленцем памяти, когда необходимость в нем отпадает. Однако ни то, ни другое необязательно, если число приспособленцев фиксировано и невелико (например, если речь идет о представлении набора символов кода ASCII). В таком случае имеет смысл хранить приспособленцев в памяти постоянно.
Результаты
При использовании приспособленцев не исключены затраты на передачу, поиск или вычисление внешнего состояния, особенно если раньше оно хранилось как внутреннее. Однако такие расходы с лихвой компенсируются экономией памяти за счет разделения объектов-приспособленцев.
Экономия памяти возникает по ряду причин:
- Уменьшение общего числа экземпляров.
- Сокращение объема памяти, необходимого для хранения внутреннего состояния.
- Вычисление, а не хранение внешнего состояния.
Чем выше степень разделения приспособленцев, тем существеннее экономия. С увеличением объема разделяемого состояния экономия также возрастает. Самого большого эффекта удается добиться, когда суммарный объем внутренней и внешней информации о состоянии велик, а внешнее состояние вычисляется, а не хранится. Тогда разделение уменьшает стоимость хранения внутреннего состояния, а за счет вычислений сокращается память, отводимая под внешнее состояние.
Паттерн приспособленец часто применяется вместе с компоновщиком для представления иерархической структуры в виде графа с разделяемыми листовыми узлами. Из-за разделения указатель на родителя не может храниться в листовом узле-приспособленце, а должен передаваться ему как часть внешнего состояния.
Это оказывает заметное влияние на способ взаимодействия объектов иерархии между собой, конкретно на способы обхода этих структур, т.к. очевидно, что в этом случае мы сможем двигаться только в одном направлении: от верхних к самым внутренним уровням иерархий.
Пример
Представим себе некоторый простенький компоновщик содержимого web-контента. Html-страница может состоять из огромного количества html-тегов: a, p, div, img и т.д. Если при этом создаются отдельные объекты для каждого тега на странице – при больших объемах обязательно возникнет проблема перерасхода памяти на хранение всех них.
Паттерн-приспособленец помогает решить эту проблему. Выделив общее внутреннее состояние всех этих тегов, а все остальное оставляя на ответственности т.н. «контекста» — организуем «пул» данных тегов. Тем самым при отрисовке каждого тега мы будем иметь лишь ссылку на объект из этого пула всех видов тегов, передавая туда остальные отличающиеся атрибуты тега в виде контекста, такие как например css стиль тега, значение обязательных атрибутов тега и т.д.
Вот как будет выглядеть базовый класс для всех тегов (Flyweight): HtmlTag.
Т.о. все внутреннее состояние тега составляет лишь его название(name), все остальное, внешнее состояние, будет передаваться в контексте при отрисовке (операция Output).
Как видим – содержит внешнее состояние. Здесь для простоты это всего лишь список css-стилей: CssStyle.
Все используемые на странице теги (ConcreteFlyweights) будут иметь свои классы объектов, наследуемые от базового HtmlTag: ATag, PTag, ImgTag, DivTag и т.д.
После определения всех необходимых составляющих паттерна, можно представить клиента (Client), который, используя фабрику, просто будет как ему угодно составлять т.н. “layout”(верстка, каркас) html-страницы: Client.
Известные применения паттерна Приспособленец (Flyweight)
Концепция объектов-приспособленцев впервые была описана и использована как техника проектирования в библиотеке Interviews 3.0. Ее разработчики построили мощный редактор документов Doc, чтобы доказать практическую полезность подобной идеи. В Doc объекты-глифы используются для представления любого символа документа. Редактор строит по одному экземпляру глифа для каждого сочетания символа и стиля (в котором определены все графические атрибуты). Таким образом, внутреннее состояние символа состоит из его кода и информации о стиле (индекс в таблицу стилей). Следовательно, внешней оказывается только позиция, поэтому Doc работает быстро. Документы представляются классом Document, который выполняет функции фабрики FlyweightFactory. Измерения показали, что реализованное в Doc разделение символов-приспособленцев весьма эффективно. В типичном случае для документа из 180 тысяч знаков необходимо создать только 480 объектов-символов.
В каркасе ЕТ++ приспособленцы используются для поддержки независимости от внешнего облика. Его стандарт определяет расположение элементов пользовательского интерфейса (полос прокрутки, кнопок, меню и пр., в совокупности именуемых виджетами) и их оформления (тени и т.д.). Виджет делегирует заботу о своем расположении и изображении отдельному объекту Layout. Изменение этого объекта ведет к изменению внешнего облика даже во время выполнения.
Для каждого класса виджета имеется соответствующий класс Layout (например, ScrollbarLayout, MenubarLayout и т.д.). В данном случае очевидная проблема состоит в том, что удваивается число объектов пользовательского интерфейса, ибо для каждого интерфейсного объекта есть дополнительный объект Layout. Чтобы избавиться от расходов, объекты Layout реализованы в виде приспособленцев. Они прекрасно подходят на эту роль, так как заняты преимущественно определением поведения и им легко передать тот небольшой объем внешней информации о состоянии, который необходим для изображения объекта.
Объекты Layout создаются и управляются объектами класса Look. Класс Look — это абстрактная фабрика, которая производит объекты Layout с помощью таких операций, как GetButtonLayout, GetMenuBarLayout и т.д. Для каждого стандарта внешнего облика у класса Look есть соответствующий подкласс (MotifLook, OpenLook и т.д.).
Кстати говоря, объекты Layout — это, по существу, стратегии (см. паттерн стратегия). Таким образом, мы имеем пример объекта-стратегии, реализованный в виде приспособленца.
Родственные паттерны
Паттерн приспособленец часто используется в сочетании с компоновщиком для реализации иерархической структуры в виде ациклического направленного графа с разделяемыми листовыми вершинами.
Паттерн проектирования Приспособленец (Flyweight) на PHP
Перед прочтением ознакомьтесь с введением в паттерны проектирования на PHP, в котором описаны принятые соглашения и понятия. Данная статья дополняется с некоторой периодичностью, так что если вы ее читали ранее, не факт что данные не изменились.
Приспособленец (Flyweight) относиться к классу структурных паттернов. Он используется для эффективной поддержки множества мелких объектов.
В некоторых приложения использование множества мелких объектов могло бы оказаться весьма полезным, однако прямая реализация ведет к чудовищному перерасходу ресурсов. Попробую объяснить это на примере интернет магазина, в котором мы будем продавать крутые дизайнерские подушки. С покупателями у нас не возникает проблем (так как подушки крутые), и список продаж все растет и растет. А так как это тешит наше самолюбие, вводить постраничность мы не спешим. И вот наступает тот момент, когда страница грузится достаточно долго, чтобы мы успели не только заскучать, но и совершить вечерний променад. Мы достигаем точки кипения и с большой неохотой пытаемся оптимизировать нашу архитектуру. Она из себя представляет собой набор объектов-продаж, включающего объект-покупатель и набор объектов-товаров. Где каждый объект-товар это совокупность таких параметров, как: артикул подушки, цвет подушки, цена, скидка.
Получается, что на каждую запись о продаже инстанцируется минимум 2 объекта (в зависимости от количества купленных подушек). В текущей реализации, порождение такого количества объектов излишне. Путем не сложных манипуляций можно объединить объект-покупатель и объект-товар в один объект продажа. На этом можно не останавливаться и свести все к одному объекту продажи.
Внося такие изменения мы не только теряем в гибкости и расширяемости, но и можем погрязнуть в устранении зависимостей. Решением этой проблемы может стать паттерн приспособленец.
Паттерн приспособленец позволяет повторно использовать мелкие объекты в различном контексте. В итоге мы сможем использовать в различных объектах-продажах одни и теже объекты-покупатели и объекты-товары. Как это работает на примере объекта-товара. У нас есть набор параметров (артикул подушки, цвет подушки, цена, скидка), которые полностью характеризуют этот товар. Соответственно, если у нас было несколько покупателей, которые выбрали одну и туже модель подушки, одного и того же цвета и попали под одну и туже скидку, то итоговые объекты-товары будут полностью идентичны. В итоге мы можем использовать один и тот же объект сразу в нескольких местах, что позволит сэкономить нам немного памяти. При достаточно большом количестве мелких объектов с малым количеством различающихся признаков, мы сможем получить весьма существенную экономию ресурсов. Итак, как структурно выглядит паттерн:
FlyweightFactory — модифицированный паттерн фабрика, для создания приспособленцев. Методу getFlyweight передаются признаки, по которым будет создан новый, либо найден и возвращен уже готовый объект.
Flyweight — абстрактный класс приспособленцев
ConcreteFlyweight — конкретная реализация приспособленца, которая будет замещать собой одинаковые мелкие объекты.
UnsharedFlyweight — реализация приспособленца, который не может быть разделен.
class FlyweightFactory < protected static $_flyweigths = array(); /** * @param string $key * @return Flyweight */ public static function getFlyweight($key) < // key может быть не только строкой, но и любым другим типом, // в таком случае необходим иной способ поиска созданных приспособленцев if (! isset(self::$_flyweigths[$key])) < // здесь могут быть условия, когда создавать обычного приспособленца, // а когда возвращать неделимого self::$_flyweigths[$key] = new ConcreteFlyweight(); >return self::$_flyweigths[$key]; > > abstract class Flyweight < /** * @var mixed внутреннее состояние */ protected $_intrinsicState = null; /** * @param mixed $extrinsicState * внешнее состояние, передаваемое в приспособленец (контекст) */ public function Operation($extrinsicState) < //. >> class ConcreteFlyweight extends Flyweight <> class UnsharedFlyweight extends Flyweight <>
Скачать исходный код можно тут.
Паттерн приспособленец зачастую используется вместе c паттерном компоновщик, для реализации иерархической структуры в виде ациклического направленного графа с разделяемыми листовыми вершинами.