Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
03:07 1 Метафора
07:40 2 Программный код метафоры
13:00 3 Паттерн Flyweight в платформе dot net
18:55 4 Пул интернирования строк
21:30 5 Паттерн Flyweight в библиотеке net framework
28:48 6 Описание паттерна по книге (ошибки и недочеты)
29:36 7 Структура паттерна на языке UML
33:54 8 Структура паттерна на языке C#
42:00 9 Использвание паттерна
45:46 10 Отношения и результаты
48:40 11 Назначение паттерна

Видео урок посвящен шаблону проектированию Flyweight, который описывает правильное применение техники создания «разделяемых объектов», для получения возможности эффективного использования большого числа объектов.

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн Flyweight

Название

Приспособленец (Русское название паттерна Flyweight, не отражает точный перевод)

Также известен как

-

Классификация

По цели: структурный

По применимости: к объектам

Частота использования

Низкая                  -   1 2 3 4 5

Назначение

Паттерн Flyweight – описывает правильное применение техники создания «разделяемых объектов», для получения возможности эффективного использования большого числа объектов.

Введение

На «житейском» уровне сложно придумать достойную аналогию или афористически яркую метафору, описывающую использование паттерна Flyweight. Рассмотрим ситуацию, когда один актер играет в одном кинофильме сразу несколько ролей. Например, Майк Майерс (Mike Myers) сыграл целых три роли в кинофильме «Остин Пауэрс».

При просмотре кинофильма, зрители сопереживая вымыслу концентрируют свое внимание на ролях – «Остине Пауэрсе» или «Докторе Зло», а не на актере исполнителе Майке Майерсе. А многие зрители даже не догадываются о том, что один актер сыграл все эти роли.

В ООП имеются такие принципиально различные виды объектов, которые называются «разделяемые» и «неразделяемые». Сам актер Майк Майерс – является «разделяемым» объектом. Актера разделяют между собой роли-персонажи являющиеся «неразделяемыми» объектами – это роль «Остин Пауэрс» и роль «Доктор Зло». Роль – это полноценный объект состоящий из костюма и грима. Костюм и грим творят чудеса, с их помощью, один и тот же актер может сыграть несколько ролей в одной картине. Важно понимать, что кинозрители не взаимодействуют напрямую с человеком-актером, кинозрители сосредотачивают свое внимание именно на игре роли-персонажа, забывая о человеке, играющем роль. Другими словами, кинозрители могут не помнить имя актера, но помнят и обсуждают поступки самого персонажа в контексте сюжета и фильма. Соответственно кинозрители «клиенты» не взаимодействуют напрямую с актером - «разделяемым» объектом, но взаимодействуют с ролями - «неразделяемыми» объектами. Паттерн Flyweight как раз и описывает способы «правильного обращения» с разделяемыми и неразделяемыми объектами.

Представим рассмотренный пример использования разделяемых и неразделяемых объектов программно.

См. пример к главе: \011_Flyweight\003_ComedyFilm

Диаграмма классов будет выглядеть следующим образом:

См. пример к главе: \011_Flyweight\003_ComedyFilm

Возникает ряд вопросов. Зачем разделять объекты на «разделяемые» и «неразделяемые»? Где эти объекты использовать в своих программах? Почему указана такая низкая частота использования паттерна Flyweight?

Для того, чтобы ответить на эти вопросы, следует вспомнить различия между объектами и экземплярами, которые строятся и размещаются в управляемой куче (Managed heap). На рисунке ниже показано, как происходит разделение «исполняемой сущности» на объект и экземпляры. Объект – это область динамической памяти, которая содержит в себе методы (и статические поля). Экземпляр – это область динамической памяти, которая содержит в себе только нестатические поля.

Как видно из рисунка у класса может быть только один объект и сколько угодно экземпляров. Локальные переменные a и b из метода Main содержат ссылки (адреса памяти) на экземпляры. Экземпляры «a» и «b» в свою очередь содержат в специальном служебном блоке (заголовке) адрес самого объекта. На рисунке синими (прямыми) стрелками показана ссылочность: «переменная» - «экземпляр» - «объект». Красная (пунктирная) линия показывает «маршрут» прохождения запроса (вызова метода). Более детально познакомится с организацией взаимодействий между объектом и экземплярами можно в книге Джефри Рихтера – «CLR via C#».

Разделение «программной сущности уровня выполнения» на объект и экземпляры, было организовано с целью добиться экономии использования оперативной памяти. Важно понимать, что тело любого метода (функции или процедуры) представляет собой набор инструкций C#, которые будут преобразованы компилятором csc.exe (С Sharp Compiler) в байт-код который будет подаваться на вход виртуальной машины (интерпретатора, компилирующего (JIT) типа) CLR (C:\Windows\System32\mscoree.dll). Байт-код тела метода занимает собой определенный объем оперативной памяти и если этот код дублировать в каждом экземпляре, то такой подход может оказаться не эффективным с точки зрения расходования памяти. Поэтому код метода выносится в отдельную исполняемую сущность, которая называется объектом. Экземпляры же в себе хранят только нестатические поля.

Также следует знать, что имеется возможность использовать только объект без построения экземпляра. Прямое обращение к объекту возможно при наличии в программе статического класса или статических членов нестатического класса. На рисунке ниже показано, как происходит прямое обращение к объекту статического класса. В таком случае в программе имеется только «разделяемая» сущность (объект) при отсутствии «неразделяемых» (экземпляров). В нотации ООП строка StaticClass.Method() – читается так: на «классе-объекте» StaticClass вызывается метод с именем Method.

В свою очередь построение только одного экземпляра «неразделяемого», без объекта невозможно и это очевидно, так как все классы неявно наследуются от базового класса Object, который содержит в себе набор методов, которые и будут размещены в объекте.

Помимо рассмотрения техник разделения на «разделяемые объекты» и «неразделяемые объекты», следует сделать акцент на таких понятиях как «внутреннее состояние» и «внешнее состояние». Известно, что состояние объекта представляется совокупностью значений полей этого объекта. 

Из всего сказанного выше легко видеть, что паттерн Flyweight нашел свое выражение в организации структуры исполняемых сущностей платформы .Net, и идея использования техники «разделяемых» и «неразделяемых» объектов красной нитью проходит через всю архитектуру организации работы динамической управляемой памяти в .Net. Объекты – представлены в динамической памяти как «разделяемые» сущности, а экземпляры – представлены как «неразделяемые» сущности. Объект – разделяется между несколькими экземплярами, также как актер между несколькими ролями.

Организация разделения «программных сущностей уровня выполнения» на объекты и экземпляры, не избавляет совсем от надобности использования паттерна Flyweight, но позволяет программистам намного реже использовать его в своей практике. 

Структура паттерна на языке UML

См. пример к главе: \011_Flyweight\001_Flyweight

Структура паттерна на языке C#

См. пример к главе: \011_Flyweight\001_Flyweight

Участники

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

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

  • ConcreteFlyweightРазделяемый конкретный приспособленец:

Класс разделяемого объекта. Реализует интерфейс класса Flyweight и добавляет при необходимости внутреннее состояние.

  • UnsharedConcreteFlyweightНеразделяемый конкретный приспособленец:

Приспособленцами принято называть разделяемые объекты. Неразделяемый приспособленец – «неразделяемо-разделяемый объект». Такая формулировка звучит взаимоисключающе. Понятно, что объект класса UnsharedConcreteFlyweight не будет являться приспособленцем, то есть не будет разделяемым и соответственно не будет происходить выделения части его внутреннего состояния во внешнее. Но тем не менее составители каталога включили в имя данного участника слово Flyweight, допуская, что не все подклассы класса Flyweight должны быть разделяемыми. Такой подход к именованию данного участника спорный, но он имеет место.

  • FlyweightFactory  – Фабрика приспособленцев:

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

  • Client  Клиент:

Работает с разделяемыми и неразделяемыми объектами. Формирует и может хранить внешнее состояние разделяемых объектов.

Отношения между участниками

Отношения между классами

  • Класс ConcreteFlyweight связан связью отношения наследования с абстрактным классом Flyweight.
  • Класс UnsharedConcreteFlyweight связан связью отношения наследования с абстрактным классом Flyweight.
  • Класс FlyweightFactory связан связью отношения агрегации с абстрактным классом Flyweight.

Отношения между объектами

  • Состояние, используемое при работе с ConcreteFlyweight разделяется на внутреннее и внешнее. Внутреннее состояние хранится непосредственно в самом объекте ConcreteFlyweight. Внешнее состояние хранится в клиенте (Client) и передается объекту-приспособленцу в качестве аргументов методов.
  • Клиенты не создают экземпляры ConcreteFlyweight напрямую через вызов конструктора, а получают их от объекта FlyweightFactory. Такой подход позволяет гарантированно получить разделяемый объект.
  • Клиентам рекомендуется создавать экземпляры UnsharedConcreteFlyweight при помощи объекта FlyweightFactory, так как в ходе эволюции программы может потребоваться преобразовать неразделяемые объекты в разделяемые и если неразделяемые объекты создавались напрямую через вызов конструктора, то придется вносить изменения в клиентский код.

Мотивация

Некоторые разновидности приложений или их части, не всегда могут быть построены полностью с использованием объектно-ориентированного подхода. Рассмотрим такую разновидность приложений, как текстовые редакторы. Объектно-ориентированные текстовые редакторы, для представления таблиц и рисунков обычно используют объекты соответствующих классов (Table, Image). Но использовать объекты для представления каждого отдельного символа в документе, может оказаться нерациональным с точки зрения расходования памяти, ведь в документе может быть несколько сотен тысяч символов, соответственно в памяти будет создано несколько сотен тысяч объектов класса Character. С другой стороны, использование объектов для представления символов повысило бы гибкость использования и сопровождения приложения. 

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

Для построения редактора документов потребуется создать следующие классы: класс Page – представляющий страницу, класс Column – представляющий колонки (столбцы) на которые может быть разбита страница, класс Row – представляющий строки, которые содержатся в колонках и класс Character представляющий символы, находящиеся в строках.

Для того чтобы избежать построения тысяч экземпляров класса Character, потребуется использовать так называемые разделяемые объекты. Разделяемый объект – это такой объект, которым могут (одновременно) пользоваться другие объекты.

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

Пример из виртуальной реальности: В этой строке имеется только одна буква «а», одна буква «о», одна буква «т» и так далее, не смотря на то, что кажется, что этих букв много. Более того, во всей этой книге имеется только одна маленькая русская буква «а». И в этой строке: а, а, а, - это все одна и та-же буква «а», в разных стилях (размер, тип шрифта, наклон и так далее).

Возникает вопрос: Как возможно добиться такого эффекта в своих программах? Предлагается рассмотреть диаграмму объектов, которые могут входить в состав текстового редактора:

На диаграмме показана древообразная структура объектов, описывающая представление элементов в текстовом документе. Условимся, что представленный на диаграмме документ состоит из одной страницы - объект класса Page. Страница состоит только из одной колонки – объект класса Column (например, страницы данной книги состоят из одной колонки). В колонке имеется три строки – объекты класса Row (row1, row2, row3). Во второй строке содержится набор символов (ссылок на экземпляры класса Character) формирующих слово «apparent». Также на диаграмме показан «пул приспособленцев». Пул – означает некий набор (коллекцию). Приспособленец (Flyweight) – это сленговое название разделяемого объекта. Иначе можно сказать, что на диаграмме показан набор разделяемых объектов класса Character.

Важно понимать, что использование разделяемых объектов – это иллюзия в программировании, как иллюзия в жизни, когда фокусник из кармана достает много раз один и тот же шарик, который (хитрым способом) через трубку в рукаве скатывается обратно в карман. Шарик – разделяемый объект. Трубка, рукав, карман – неразделяемые объекты. Фокус с шариком, не получится без трубки. Для работы с разделяемыми объектами понадобится использование неразделяемых объектов. В примере с текстовым редактором, символ (Character) – разделяемый объект, а страницы (Page), колонки (Column) и строки (Row) – неразделяемые объекты.

Создавать полноценную иллюзию того, что в документе имеется много экземпляров класса Character, помогает специальный объект класса Graphics, который занимается рисованием фигур и символов в области формы (Form). 

Graphics graphics = window.CreateGraphics();
graphics.DrawString(character, font, brush, X, Y);

Экземпляр класса Graphics, еще называют графическим устройством, потому что у него имеется жизненная аналогия – плоттер (устройство которое имеет головку, которая по направляющим передвигается по листу бумаги и вычерчивает рисунки с большой точностью). Также как плоттер, объект класса Graphics «вычерчивает» на виртуальной форме фигуры и символы.

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

См. пример к главе: \011_Flyweight\002_DocumentEditor

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

Применимость паттерна

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

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

Результаты

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

Экономия памяти возникает в следующих случаях:

  • Уменьшение общего числа экземпляров.
  • Сокращение объема памяти, необходимого для хранения внутреннего состояния. (некрасиво)
  • Вычисление, а не хранение внутреннего состояния.

Чем больше получится создать разделяемых объектов, тем больше будет экономия памяти. Самого большого эффекта экономии памяти возможно добиться, когда внешнее состояние вычисляется, а не хранится, соответственно за счет вычислений сокращается объем выделяемой памяти под хранение внешнего состояния.

Реализация

Полезные приемы реализации паттерна Flyweight:

  • Вынесение внешнего состояния за пределы разделяемого объекта.

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

  • Управление разделяемыми объектами.

Клиенты не должны создавать экземпляры разделяемых объектов напрямую через вызов конструкторов. Для создания разделяемого объекта, следует воспользоваться объектом-фабрикой, которая позволит найти необходимого приспособленца. Объект-фабрика содержит в себе ассоциативный массив (ключ-значение), обращаясь к которому по ключу, можно находить нужного приспособленца. Если требуемый приспособленец отсутствует в ассоциативном массиве, то он создается, сохраняется в массиве и клиенту возвращается ссылка на него (нового приспособленца). Также требуется задуматься над созданием механизма по удалению приспособленцев, надобность в которых отпадает. Так как ассоциативный массив в ходе работы программы хранит ссылку на приспособленца, который не будет более использоваться, то механизм сборки мусора не может удалить этого приспособленца, поэтому требуется организовать явное удаление неиспользуемого приспособленца из ассоциативного массива.

Известные применения паттерна в .Net

Паттерн Flyweight, выражен в платформе .Net в виде работы подсистемы управления памятью и особенностью техники разделения исполняемых сущностей на экземпляры и объекты.

© 2017 ITVDN, все права защищены