CodeLAB
на главную карта сайта обратная связь

Популярные задачи:

#Рисование тора. (35998 hits)
#Курсы валют. (68702 hits)
#Найти максимальную сумму в последовательности. (140749 hits)
#"The Java Programming Language" Ken Arnold, James Gosling, David Holmes листинги, код, примеры из книги, исходники. (61988 hits)
#Сортировка вставкой. (113896 hits)
#Постепенное затемнение. (52167 hits)
#Простая геометрическая и текстовая анимация. (401987 hits)
#Как посчитать одинаковые пары за 1 проход (самая быстрая версия!). (3271 hits)
#Простой генератор случайных чисел. (135639 hits)
#Вращение фигуры в плоскости. (40996 hits)
#Глубокое полное клонирование. (36804 hits)
#Преобразование RGB в HEX и обратно HEX в RGB. (57883 hits)
#Обработка шаблонных писем. (56869 hits)
#Счетчик времени с точностью до микросекунд. (130860 hits)
#Логирование в GUI. (33324 hits)
#Улучшение быстрой сортировки. (78668 hits)
#Вычисление медианы заданной выборки. (50303 hits)
#Динамическое изменение цвета полоски прокрутки в IE5.5 и выше. (31726 hits)
#Рисование множества Мандельброта. (45607 hits)
#Масштабирование, пропорциональное изменение размеров картинки. (103301 hits)


Главная >> Каталог задач >> Паттерны >> Структурные >>

Фасад (Facade)

Aвтор:
Дата:
Просмотров: 159124
реализации(C++: 7шт...) +добавить

Имя

«Паттерн
Facade»

Фасад - паттерн, структурирующий объекты, предоставляя ко всем ним доступ через единый шлюз.

Условия, Задача, Назначение

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

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

Мотивация

Рассмотрим, например, среду программирования, которая дает приложениям доступ к подсистеме компиляции. В этой подсистеме имеются такие классы, как Scanner (лексический анализатор), Parser (синтаксический анализатор), ProgramNode (узел программы), BytecodeStream (поток байтовых кодов) и ProgramNodeBuilder (строитель узла программы). Все вместе они составляют компилятор. Некоторым специализированным приложениям, возможно, понадобится прямой доступ к этим классам. Но для большинства клиентов компилятора такие детали, как синтаксический разбор и генерация кода, обычно не нужны; им просто требуется откомпилировать некоторую программу. Для таких клиентов применение мощного, но низкоуровневого интерфейса подсистемы компиляции только усложняет задачу.

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

Признаки применения, использования паттерна Фасад (Facade)

Используйте паттерн фасад, когда:
  1. Хотите предоставить простой интерфейс к сложной подсистеме.
    Часто подсистемы усложняются по мере развития. Применение большинства паттернов приводит к появлению меньших классов, но в большем количестве. Такую подсистему проще повторно использовать и настраивать под конкретные нужды, но вместе с тем применять подсистему без настройки становится труднее. Фасад предлагает некоторый вид системы по умолчанию, устраивающий большинство клиентов. И лишь те объекты, которым нужны более широкие возможности настройки, могут обратиться напрямую к тому, что находится за фасадом;
  2. Между клиентами и классами реализации абстракции существует много зависимостей.
    Фасад позволит изолировать подсистему как от клиентов, так и от других подсистем, что, в свою очередь, способствует повышению степени независимости и переносимости;
  3. Вы хотите разложить подсистему на отдельные слои: расслоить.
    Используйте фасад для определения точки входа на каждый уровень подсистемы. Если подсистемы зависят друг от друга, то зависимость можно упростить, разрешив подсистемам обмениваться информацией только через фасады.

Решение

Участники паттерна Фасад (Facade)

  1. Facade (Compiler) – фасад.
    Осведомлен о том, каким классам подсистемы адресовать запрос.
    Делегирует запросы клиентов подходящим объектам внутри подсистемы.
  2. Классы подсистемы (Scanner, Parser, ProgramNode и т.д.).
    Реализуют функциональность подсистемы.
    Выполняют работу, запрошенную объектом Facade, которую в свою очередь запросил у Facade-а один из клиентов.
    Ничего не «знают» о существовании самого фасада, то есть не хранят ссылок на него.

Схема использования паттерна Фасад (Facade)

Клиенты общаются с подсистемой, посылая запросы фасаду. Он переадресует их подходящим объектам внутри подсистемы. Хотя основную работу выполняют именно объекты подсистемы, фасаду, возможно, придется преобразовать свой интерфейс в интерфейсы подсистемы.
Клиенты, пользующиеся фасадом, не имеют прямого доступа к объектам подсистемы.

Вопросы, касающиеся реализации паттерна Фасад (Facade)

При реализации фасада следует обратить внимание на следующие вопросы:
  1. Уменьшение степени связанности клиента с подсистемой.
    Степень связанности можно значительно уменьшить, если сделать класс Facade абстрактным. Его конкретные подклассы будут соответствовать различным реализациям подсистемы. Тогда клиенты смогут взаимодействовать с подсистемой через интерфейс абстрактного класса Facade. Это изолирует клиентов от информации о том, какая реализация подсистемы используется.
    Вместо порождения подклассов можно просто конфигурировать объект Facade различными объектами подсистем – передавать ему на начальном этапе (создании) соответствующие объекты. Для настройки фасада достаточно будет заменить потом один или несколько таких объектов, группу взаимосвязанных объектов и т.д.
  2. Открытые и закрытые классы подсистем.
    Подсистема похожа на класс в том отношении, что у обоих есть интерфейсы и оба что-то инкапсулируют. Класс - инкапсулирует состояние и операции, а подсистема - классы. И если полезно различать открытый и закрытый интерфейсы класса, то не менее разумно говорить об открытом и закрытом интерфейсах подсистемы.
    Открытый интерфейс подсистемы состоит из классов, к которым имеют доступ все клиенты; закрытый же интерфейс доступен только для расширения подсистемы. Класс Facade, конечно же, является частью открытого интерфейса, но это не единственная часть. Другие классы подсистемы также могут быть открытыми. Например, в системе компиляции классы Parser и Scanner - часть открытого интерфейса.
    Делать классы подсистемы закрытыми иногда полезно, но это поддерживается немногими объектно-ориентированными языками. Например, и в C++, и в Smalltalk для классов традиционно использовалось глобальное пространство имен. Однако комитет по стандартизации C++ добавил к языку пространства имен, и это позволило разрешать доступ только к открытым классам подсистемы.

Результаты

У паттерна фасад есть следующие преимущества:
  1. Изолирует клиентов от компонентов подсистемы
    Уменьшая тем самым число объектов, с которыми клиентам приходится иметь дело, упрощая работу с подсистемой.
  2. Позволяет ослабить связанность между подсистемой и ее клиентами.
    Зачастую компоненты подсистемы сильно связаны: многие классы из разных частей и слоев системы взаимодействуют с друг другом, меняя один такой класс, приходится перекомпилировать и остальные связанные и т.д. Слабая связанность позволяет видоизменять компоненты, не затрагивая при этом клиентов. Фасады помогают разложить систему на слои и структурировать зависимости между объектами, а также избежать сложных и циклических зависимостей. Это оказывается важным, если клиент и подсистема реализуются независимо.
    Уменьшение числа зависимостей на стадии компиляции чрезвычайно важно в больших системах. Хочется, конечно, чтобы время, уходящее на перекомпиляцию после изменения классов подсистемы, было минимальным. Сокращение числа зависимостей за счет фасадов может уменьшить количество нуждающихся в повторной компиляции файлов после небольшой модификации какой-нибудь важной подсистемы. Фасад может также упростить процесс переноса системы на другие платформы, поскольку уменьшается вероятность того, что в результате изменения одной подсистемы понадобится изменять и все остальные.
  3. Фасад не исключает возможности приложениям напрямую обращаться к классам подсистемы, если это необходимо. Таким образом, у вас есть выбор между простотой и общностью. Совет конечно очевидный: как можно только в крайних случаях прибегайте к прямому взаимодействию с классом за фасадом, лучше если возможно все же как-нибудь обобщить/абстрагировать эту операцию и добавить ее в сам фасад.

Пример

Рассмотрим более подробно, как возвести фасад вокруг подсистемы компиляции.
В подсистеме компиляции определен класс BytecodeStream, который представляет собой поток объектов Bytecode. Объект Bytecode инкапсулирует байтовый код, с помощью которого описываются машинные команды. В этой же подсистеме определен еще класс Token для объектов, инкапсулирующих лексемы языка программирования.

Класс Scanner принимает на входе поток символов и генерирует поток лексем, по одной каждый раз, т.е. он выступает в роли самого первого уровня обработки исходного кода: Scanner.

Далее класс Parser использует класс ProgramNodeBuilder для построения дерева разбора из лексем, возвращенных классом Scanner.

Parser каждый раз вызывает ProgramNodeBuilder для пошагового построения дерева. Взаимодействие этих классов описывается паттерном строитель.

Дерево разбора состоит из экземпляров подклассов класса ProgramNode, таких как StatementNode, ExpressionNode и т.д. Иерархия классов ProgramNodeэто пример паттерна компоновщик. Класс ProgramNode определяет интерфейс для манипулирования узлом программы и его потомками, если таковые имеются.

Операция Traverse (обход) принимает объект CodeGenerator (кодогенератор) в качестве параметра. Подклассы ProgramNode используют этот объект для генерации машинного кода в форме объектов Bytecode, которые помещаются в поток BytecodeStream. Класс CodeGenerator описывается паттерном посетитель.

У CodeGenerator есть подклассы, например StackMachineCodeGenerator и RISCCodeGenerator генерирующие машинный код для различных аппаратных архитектур.

Каждый подкласс ProgramNode реализует операцию Traverse и обращается к ней для обхода своих потомков. Каждый потомок рекурсивно делает то же самое для своих потомков. Например, в подклассе ExpressionNode (узел выражения) операция Traverse определена так: ExpressionNode.

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

В этой реализации жестко «зашит» тип кодогенератора, поэтому программисту не нужно явно задавать целевую архитектуру. Это может быть вполне разумно, когда есть всего одна такая архитектура. Если же это не так, можно было бы изменить конструктор класса Compiler, чтобы он принимал объект CodeGenerator в качестве параметра. Тогда программист указывал бы, каким генератором пользоваться при инстанцировании объекта Compiler. Фасад компилятора можно параметризовать и другими участниками, скажем, объектами Scanner и ProgramNodeBuilder, что повышает гибкость, но в то же время сводит на нет основную цель фасада - предоставление упрощенного интерфейса для наиболее распространенного случая.

 

Известные применения паттерна Фасад (Facade)

Пример компилятора в разделе «Пример кода» навеян идеями из системы компиляции языка ObjectWorks\Smalltalk.
В каркасе ЕТ++ приложение может иметь встроенные средства инспектирования объектов во время выполнения. Они реализуются в отдельной подсистеме, включающей класс фасада с именем ProgrammingEnvironment. Этот фасад определяет такие операции, как InspectObject и InspectClass для доступа к инспекторам.
Приложение, написанное в среде ЕТ++, может также запретить поддержку инспектирования. В таком случае класс ProgrammingEnvironment реализует соответствующие запросы как пустые операции, не делающие ничего. Только подкласс ETProgrammingEnvironment реализует эти операции так, что они отображают окна соответствующих инспекторов. Приложению неизвестно, доступно инспектирование или нет. Здесь мы встречаем пример абстрактной связанности между приложением и подсистемой инспектирования.
В операционной системе Choices фасады используются для составления одного каркаса из нескольких. Ключевыми абстракциями в системе Choices являются процессы, память и адресные пространства. Для каждой из них есть соответствующая подсистема, реализованная в виде каркаса. Это обеспечивает поддержку переноса Choices на разные аппаратные платформы. У двух таких подсистем есть «представители», то есть фасады. Они называются FileSystemlnterface (память) и Domain (адресные пространства).

Например, для каркаса виртуальной памяти фасадом служит Domain. Класс Domain представляет адресное пространство. Он обеспечивает отображение между виртуальными адресами и смещениями объектов в памяти, файле или на устройстве длительного хранения. Базовые операции класса Domain поддерживают добавление объекта в память по указанному адресу, удаление объекта из памяти и обработку ошибок отсутствия страниц.
Как видно из вышеприведенной диаграммы, внутри подсистемы виртуальной
памяти используются следующие компоненты:
  1. MemoryObject представляет объекты данных;
  2. MemoryObjectCache кэширует данные из объектов MemoryObj ects в физической памяти.
    MemoryObjectCache - это не что иное, как объект Стратегия, в котором локализована политика кэширования;
  3. AddressTranslation инкапсулирует особенности оборудования трансляции адресов.

Операция RepairFault вызывается при возникновении ошибки из-за отсутствия страницы. Domain находит объект в памяти по адресу, где произошла ошибка и делегирует операцию RepairFault кэшу, ассоциированному с этим объектом. Поведение объектов Domain можно настроить, заменив их компоненты.

Родственные паттерны

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

Реализации:

C++(7)   +добавить

1) Scanner.cpp на C++, code #463[автор:this]
2) Parser.cpp на C++, code #464[автор:this]
3) ProgramNodeBuilder.cpp на C++, code #465[автор:this]
4) ProgramNode.cpp на C++, code #466[автор:this]
5) Compiler.cpp на C++, code #468[автор:this]
6) ExpressionNode.cpp на C++, code #469[автор:this]
7) CodeGenerator.cpp на C++, code #467[автор:this]