Структурные шаблоны: Фасад (Facade). Паттерны проектирования: Фасад (Facade) Паттерны проектирования фасад

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

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

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

Благодаря такому подходу, пользователь взаимодействует только с интерфейсом, не имея никакого представления о подсистемах, которые скрываются за ним.

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

Надежный фасад - наш упрощенный интерфейс - позволит нам не беспокоиться о тесных связях некоторых модулей нашей системы с dojo, jQuery, YUI, zepto или какой-либо другой библиотекой. Это становится не так важно. Вы можете переходить с одной библиотеки на другую не меняя слой взаимодействия. К примеру, с jQuery на dojo. Более того, у вас появляется возможность совершить такой переход на более поздних этапах, без изменений в остальных частях системы.

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

var module = (function () { var _private = { i : 5 , get : function () { console . log ("Текущее значение:" + this . i ); }, set : function (val ) { this . i = val ; }, run : function () { console . log ("процесс запущен" ); }, jump : function () { console . log ("резкое изменение" ); } }; return { facade : function (args ) { _private . set (args . val ); _private . get (); if (args . run ) { _private . run (); } } } }()); module . facade ({ run : true , val : 10 }); // Текущее значение: 10, процесс запущен

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

Почитать описание других паттернов.

Проблема

Минимизировать зависимость подсистем некоторой сложной системы и обмен информацией между ними.

Описание

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

Таким образом, минимизация зависимости подсистем, а также снижение объема передаваемой между ними информации - одна из основных задач проектирования.

Один из способов решения данной задачи - использование паттерна «Фасад».

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

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

Практическая задача

Используя паттерн «Фасад», реализуем унифицированный интерфейс к некоторой подсистеме авторизации пользователей. Сама подсистема авторизации (в данном примере), безусловно не претендует на «сложную систему», однако она отчетливо отображает основные достоинства паттерна.

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

Рассмотрим диаграмму. Каркас подсистемы авторизации, для наглядности, выделен в прямоугольник. Фасад Authorizator предоставляет клиенту унифицированный интерфейс для работы с подсистемой. В данном случае, это всего один метод - authorizate(), однако их могло быть и больше. При этом, клиент может использовать фасад для работы с подсистемой, а может, непосредственно пользоваться классами, составляющими ее. Сам процесс авторизации достаточно прост. На основании имени пользователя ищется соответствующая запись в базе данных, посредством интерфейса DB. Затем, сравнивается пароль найденной записи с паролем указанным пользователем.

Реализация на С#

В коде реализации нет класса PgSQLDB.
using System;
using System.Collections.Generic ;
using System.Linq;
using System.Text;
using System.Security;

namespace Facade
{
//Абстрактный класс пользователя
abstract class User
{
protected string username;
protected string passwd;

public abstract string getUserRole();

public string getPasswdHash()
{
// Это строка не несет какой-либой смысловой нагрузки.
// Безусловно, таким образом мы получаем небезопасный хеш-код пароля
return passwd.GetHashCode().ToString();
}
}

// Уточнение пользователя, в качестве пользователя по-умолчанию
class DefaultUser: User
{
public DefaultUser(string username, string passwd)
{
this .username = username;
this .passwd = passwd;
}


{
return "DEFAULT_USER" ;
}
}

// Уточнение пользователя, в качестве администратора
class Administrator: User
{
public Administrator(string username, string passwd)
{
this .username = username;
this .passwd = passwd;
}

public override string getUserRole()
{
return "ADMINISTRATOR" ;
}

// Интерфейс доступа к базе данных
interface DB
{
User search(string username);
}

// Реализация интерфейса БД для SQLite
class SQLiteDB: DB
{
public SQLiteDB(string filename)
{
// Инициализация драйвера БД
}

public User search(string username)
{
// Заглушка
throw new NotImplementedException();
}
}

// Авторизация пользователя
public void authorizate(string username, string passwd)
{
DB db = new SQLiteDB("db.sqlite" );
User user = db.search(username);
if (user.getPasswdHash() == passwd)
{
// все хорошо, пользователь опознан
}
else
{
// что-то пошло не так
throw new SecurityException("Wrong password or username!" );
}
}
}

class Program
{
static void Main(string args)
{
// Вымышленный пользователь
string username = "Vasya" ;
string passwd = "qwerty" .GetHashCode().ToString();

Authorizator auth = new Authorizator();
try
{
auth.authorizate(username, passwd);
}
catch (SecurityException ex)
{
// Пользователь не прошел аутентификацию
}
}
}
}


* This source code was highlighted with Source Code Highlighter .

PS : У меня у одного хабраредактор не работает?

Назначение паттерна Facade

  • Паттерн Facade предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Facade определяет интерфейс более высокого уровня, упрощающий использование подсистемы.
  • Паттерн Facade "обертывает" сложную подсистему более простым интерфейсом.

Решаемая проблема

Клиенты хотят получить упрощенный интерфейс к общей функциональности сложной подсистемы.

Обсуждение паттерна Facade

Паттерн Facade инкапсулирует сложную подсистему в единственный интерфейсный объект. Это позволяет сократить время изучения подсистемы, а также способствует уменьшению степени связанности между подсистемой и потенциально большим количеством клиентов. С другой стороны, если фасад является единственной точкой доступа к подсистеме, то он будет ограничивать возможности, которые могут понадобиться "продвинутым" пользователям.

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

Структура паттерна Facade

Клиенты общаются с подсистемой через Facade. При получении запроса от клиента объект Facade переадресует его нужному компоненту подсистемы. Для клиентов компоненты подсистемы остаются "тайной, покрытой мраком".

Подсистемы SubsystemOne и SubsystemThree не взаимодействуют напрямую с внутренними компонентами подсистемы SubsystemTwo. Они используют "фасад" SubsystemTwoWrapper (т.е. абстракцию более высокого уровня).

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

  • Определите для подсистемы простой, унифицированный интерфейс.
  • Спроектируйте класс "обертку", инкапсулирующий подсистему.
  • Вся сложность подсистемы и взаимодействие ее компонентов скрыты от клиентов. "Фасад" / "обертка" переадресует пользовательские запросы подходящим методам подсистемы.
  • Клиент использует только "фасад".
  • Рассмотрите вопрос о целесообразности создания дополнительных "фасадов".

Особенности паттерна Facade

  • Facade определяет новый интерфейс, в то время как Adapter использует уже имеющийся. Помните, Adapter делает работающими вместе два существующих интерфейса, не создавая новых.
  • Если Flyweight показывает, как сделать множество небольших объектов, то Facade показывает, как сделать один объект, представляющий целую подсистему.
  • Mediator похож на Facade тем, что абстрагирует функциональность существующих классов. Однако Mediator централизует функциональность между объектами-коллегами, не присущую ни одному из них. Коллеги обмениваются информацией друг с другом через Mediator. С другой стороны, Facade определяет простой интерфейс к подсистеме, не добавляет новой функциональности и не известен классам подсистемы.
  • Abstract Factory может применяться как альтернатива Facade для сокрытия платформенно-зависимых классов.
  • Объекты "фасадов" часто являются Singleton , потому что требуется только один объект Facade.
  • Adapter и Facade в являются "обертками", однако эти "обертки" разных типов. Цель Facade - создание более простого интерфейса, цель Adapter - адаптация существующего интерфейса. Facade обычно "обертывает" несколько объектов, Adapter "обертывает" один объект.

Реализация паттерна Facade

Разбиение системы на компоненты позволяет снизить ее сложность. Ослабить связи между компонентами системы можно с помощью паттерна Facade. Объект "фасад" предоставляет единый упрощенный интерфейс к компонентам системы.

В примере ниже моделируется система сетевого обслуживания. Фасад FacilitiesFacade скрывает внутреннюю структуру системы. Пользователь, сделав однажды запрос на обслуживание, затем 1-2 раза в неделю в течение 5 месяцев справляется о ходе выполнения работ до тех пор, пока его запрос не будет полностью обслужен.

#include class MisDepartment { public: void submitNetworkRequest() { _state = 0; } bool checkOnStatus() { _state++; if (_state == Complete) return 1; return 0; } private: enum States { Received, DenyAllKnowledge, ReferClientToFacilities, FacilitiesHasNotSentPaperwork, ElectricianIsNotDone, ElectricianDidItWrong, DispatchTechnician, SignedOff, DoesNotWork, FixElectriciansWiring, Complete }; int _state; }; class ElectricianUnion { public: void submitNetworkRequest() { _state = 0; } bool checkOnStatus() { _state++; if (_state == Complete) return 1; return 0; } private: enum States { Received, RejectTheForm, SizeTheJob, SmokeAndJokeBreak, WaitForAuthorization, DoTheWrongJob, BlameTheEngineer, WaitToPunchOut, DoHalfAJob, ComplainToEngineer, GetClarification, CompleteTheJob, TurnInThePaperwork, Complete }; int _state; }; class FacilitiesDepartment { public: void submitNetworkRequest() { _state = 0; } bool checkOnStatus() { _state++; if (_state == Complete) return 1; return 0; } private: enum States { Received, AssignToEngineer, EngineerResearches, RequestIsNotPossible, EngineerLeavesCompany, AssignToNewEngineer, NewEngineerResearches, ReassignEngineer,EngineerReturns, EngineerResearchesAgain, EngineerFillsOutPaperWork, Complete }; int _state; }; class FacilitiesFacade { public: FacilitiesFacade() { _count = 0; } void submitNetworkRequest() { _state = 0; } bool checkOnStatus() { _count++; /* Запрос на обслуживание получен */ if (_state == Received) { _state++; /* Перенаправим запрос инженеру */ _engineer.submitNetworkRequest(); cout << "submitted to Facilities - " << _count << " phone calls so far" << endl; } else if (_state == SubmitToEngineer) { /* Если инженер свою работу выполнил, перенаправим запрос электрику */ if (_engineer.checkOnStatus()) { _state++; _electrician.submitNetworkRequest(); cout << "submitted to Electrician - " << _count << " phone calls so far" << endl; } } else if (_state == SubmitToElectrician) { /* Если электрик свою работу выполнил, перенаправим запрос технику */ if (_electrician.checkOnStatus()) { _state++; _technician.submitNetworkRequest(); cout << "submitted to MIS - " << _count << " phone calls so far" << endl; } } else if (_state == SubmitToTechnician) { /* Если техник свою работу выполнил, то запрос обслужен до конца */ if (_technician.checkOnStatus()) return 1; } /* Запрос еще не обслужен до конца */ return 0; } int getNumberOfCalls() { return _count; } private: enum States { Received, SubmitToEngineer, SubmitToElectrician, SubmitToTechnician }; int _state; int _count; FacilitiesDepartment _engineer; ElectricianUnion _electrician; MisDepartment _technician; }; int main() { FacilitiesFacade facilities; facilities.submitNetworkRequest(); /* Звоним, пока работа не выполнена полностью */ while (!facilities.checkOnStatus()) ; cout << "job completed after only " << facilities.getNumberOfCalls() << " phone calls" << endl; }

Вывод программы:

submitted to Facilities - 1 phone calls so far submitted to Electrician - 12 phone calls so far submitted to MIS - 25 phone calls so far job completed after only 35 phone calls

  1. Identify the desired unified interface for a set of subsystems
  2. Design a "wrapper" class that can encapsulate the use of the subsystems
  3. The client uses (is coupled to) the Facade
  4. The facade/wrapper "maps" to the APIs of the subsystems
// 1. Subsystem class PointCartesian { private double x, y; public PointCartesian(double x, double y) { this.x = x; this.y = y; } public void move(int x, int y) { this.x += x; this.y += y; } public String toString() { return "(" + x + "," + y + ")"; } public double getX() { return x; } public double getY() { return y; } } // 1. Subsystem class PointPolar { private double radius, angle; public PointPolar(double radius, double angle) { this.radius = radius; this.angle = angle; } public void rotate(int angle) { this.angle += angle % 360; } public String toString() { return "[" + radius + "@" + angle + "]"; } } // 1. Desired interface: move(), rotate() class Point { // 2. Design a "wrapper" class private PointCartesian pointCartesian; public Point(double x, double y) { pointCartesian = new PointCartesian(x, y); } public String toString() { return pointCartesian.toString(); } // 4. Wrapper maps public void move(int x, int y) { pointCartesian.move(x, y); } public void rotate(int angle, Point o) { double x = pointCartesian.getX() - o.pointCartesian.getX(); double y = pointCartesian.getY() - o.pointCartesian.getY(); PointPolar pointPolar = new PointPolar(Math.sqrt(x * x + y * y),Math.atan2(y, x) * 180 / Math.PI); // 4. Wrapper maps pointPolar.rotate(angle); System.out.println(" PointPolar is " + pointPolar); String str = pointPolar.toString(); int i = str.indexOf("@"); double r = Double.parseDouble(str.substring(1, i)); double a = Double.parseDouble(str.substring(i + 1, str.length() - 1)); pointCartesian = new PointCartesian(r*Math.cos(a*Math.PI/180) + o.pointCartesian.getX(), r*Math.sin(a * Math.PI / 180) + o.pointCartesian.getY()); } } class Line { private Point o, e; public Line(Point ori, Point end) { o = ori; e = end; } public void move(int x, int y) { o.move(x, y); e.move(x, y); } public void rotate(int angle) { e.rotate(angle, o); } public String toString() { return "origin is " + o + ", end is " + e; } } public class FacadeDemo { public static void main(String args) { // 3. Client uses the Facade Line lineA = new Line(new Point(2, 4), new Point(5, 7)); lineA.move(-2, -4); System.out.println("after move: " + lineA); lineA.rotate(45); System.out.println("after rotate: " + lineA); Line lineB = new Line(new Point(2, 1), new Point(2.866, 1.5)); lineB.rotate(30); System.out.println("30 degrees to 60 degrees: " + lineB); } }

Книга GoF описывает этот паттерн как предоставляющий унифицированный интерфейс к множеству интерфейсов в некоторой подсистеме. Книга "Паттерны проектирования " дает это же толкование и обращает внимание, что, скрывая слож­ность подсистемы, паттерн "Фасад " в то же время предоставляет все возможности подсистемы через удобный для использования интерфейс.

Для простого практического примера того, как работает паттерн "Фасад", пред­ставьте стиральную машину со всего лишь двумя режимами стирки: для сильно загрязненного белья и для слабо загрязненного.

Для каждого режима стиральная машина должна выполнить предопределенный набор операций: установить темпе­ратуру воды, нагреть воду, установить длительность цикла стирки, добавить сти­ральный порошок, добавить отбеливающее средство, добавить смягчитель ткани и т.д. Каждый режим требует различного набора инструкций по стирке (разное количество стирального порошка, более высокая/низкая температура, более долгий/короткий цикл отжима и т.д.).

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

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

Паттерн "Фасад" обычно реализуется в следующих целях и случаях:

  • для обеспечения простого и унифицированного доступа к унаследованной системе управления производством;
  • для создания общедоступного API к таким классам, как драйвер;
  • для предоставления крупно-модульного доступа к доступным сервисам. Серви­сы сгруппированы как в вышеприведенном примере со стиральной машиной;
  • чтобы снизить количество сетевых вызовов. Фасад выполняет множество об­ращений к подсистеме, в то время как удаленный клиент должен выполнить одно-единственное обращение к фасаду;
  • для инкапсуляции последовательности выполняемых действий и внутренних деталей приложения, чтобы обеспечить простоту и безопасность.

Кстати, фасады также иногда реализуют как абстрактные фабрики-одиночки.

Диаграмма классов фасада . Как можно увидеть на диаграмме классов на рис.3.1 , паттерн "Фасад" предоставляет простой интерфейс для базовой системы, инкап­сулируя сложную логику.

Рис 3.1 . Диаграмма классов паттерна "Фасад".