Фасад (Facade) представляет шаблон проектирования, который позволяет скрыть сложность системы с помощью предоставления упрощенного интерфейса для взаимодействия с ней.
Когда использовать фасад?
- Когда имеется сложная система, и необходимо упростить с ней работу. Фасад позволит определить одну точку взаимодействия между клиентом и системой.
- Когда надо уменьшить количество зависимостей между клиентом и сложной системой. Фасадные объекты позволяют отделить, изолировать компоненты системы от клиента и развивать и работать с ними независимо.
- Когда нужно определить подсистемы компонентов в сложной системе. Создание фасадов для компонентов каждой отдельной подсистемы позволит упростить взаимодействие между ними и повысить их независимость друг от друга.
Формальное определение программы в C# может выглядеть так:
class SubsystemA
{
public void A1()
{}
}
class SubsystemB
{
public void B1()
{}
}
class SubsystemC
{
public void C1()
{}
}
public class Facade
{
SubsystemA subsystemA;
SubsystemB subsystemB;
SubsystemC subsystemC;
public Facade(SubsystemA sa, SubsystemB sb, SubsystemC sc)
{
subsystemA = sa;
subsystemB = sb;
subsystemC = sc;
}
public void Operation1()
{
subsystemA.A1();
subsystemB.B1();
subsystemC.C1();
}
public void Operation2()
{
subsystemB.B1();
subsystemC.C1();
}
}
class Client
{
public void Main()
{
Facade facade = new Facade();
facade.Operation1();
facade.Operation2();
}
}
Участники
- Классы
SubsystemA,SubsystemB,SubsystemCи т.д. являются компонентами сложной подсистемы, с которыми должен взаимодействовать клиент Clientвзаимодействует с компонентами подсистемыFacade– непосредственно фасад, который предоставляет интерфейс клиенту для работы с компонентами
Рассмотрим применение паттерна в реальной задаче. Думаю, большинство программистов согласятся со мной, что писать в Visual Studio код одно удовольствие по сравнению с тем, как писался код ранее до появления интегрированных сред разработки. Мы просто пишем код, нажимаем на кнопку и все – приложение готово. В данном случае интегрированная среда разработки представляет собой фасад, который скрывает всю сложность процесса компиляции и запуска приложения. Теперь опишем этот фасад в программе на C#:
class Program
{
static void Main(string[] args)
{
TextEditor textEditor = new TextEditor();
Compiller compiller = new Compiller();
CLR clr = new CLR();
VisualStudioFacade ide = new VisualStudioFacade(textEditor, compiller, clr);
Programmer programmer = new Programmer();
programmer.CreateApplication(ide);
Console.Read();
}
}
// текстовый редактор
class TextEditor
{
public void CreateCode()
{
Console.WriteLine("Написание кода");
}
public void Save()
{
Console.WriteLine("Сохранение кода");
}
}
class Compiller
{
public void Compile()
{
Console.WriteLine("Компиляция приложения");
}
}
class CLR
{
public void Execute()
{
Console.WriteLine("Выполнение приложения");
}
public void Finish()
{
Console.WriteLine("Завершение работы приложения");
}
}
class VisualStudioFacade
{
TextEditor textEditor;
Compiller compiller;
CLR clr;
public VisualStudioFacade(TextEditor te, Compiller compil, CLR clr)
{
this.textEditor = te;
this.compiller = compil;
this.clr = clr;
}
public void Start()
{
textEditor.CreateCode();
textEditor.Save();
compiller.Compile();
clr.Execute();
}
public void Stop()
{
clr.Finish();
}
}
class Programmer
{
public void CreateApplication(VisualStudioFacade facade)
{
facade.Start();
facade.Stop();
}
}
В данном случае компонентами системы являются класс текстового редактора TextEditor, класс компилятора Compiller и класс общеязыковой среды выполнения CLR. Клиентом выступает класс программиста, фасадом – класс VisualStudioFacade, который через свои методы делегирует выполнение работы компонентам и их методам.
При этом надо учитывать, что клиент может при необходимости обращаться напрямую к компонентам, например, отдельно от других компонентов использовать текстовый редактор. Но в виду сложности процесса создания приложения лучше использовать фасад. Также это не единственный возможный фасад для работы с данными компонентами. При необходимости можно создавать альтернативные фасады также, как в реальной жизни мы можем использовать альтернативные среды разработки.
Реализация шаблона в общем виде
Возможны следующие подходы к реализации шаблона:
- Создание отдельного класса, который ссылается на экземпляр системы. Это классический вариант и реализация отчетливо выделена, т.к. является отдельным объектом.
public class FacadeImpl : IFacade { private AppSubSystem _system; public FacadeImpl(AppSubSubSystem s) { this._system = s; } /* Skipped */ }Кроме того, экземпляр системы может создаваться внутри Фасада:
public class FacadeImpl : IFacade { private readonly AppSubSystem _system = new AppSubSystem(); /* Skipped */ } - Добавление интерфейса Фасада в систему. Может пригодиться, если у Фасада много клиентов и нет смысла создавать каждому по собственному экземпляру реализации шаблона.
public class AppSubSystem { public IFacade FacadeInterface { get; private set; } public AppSubSystem () { this.FacadeInterface = new FacadeImpl(this); } /* Skipped */ } - Поддержка интерфейса Фасада в классе самой системы, используя наследование. Реализация получается “размытой” по системе. Однако, теперь ее экземпляр может использоваться там, где требуется интерфейс Фасада. Кроме того, появляется возможность доступа к закрытым полям и методам системы, а так же нет необходимости переадресации вызовов одинаковых методов.
public class AppSubSystem : IFacade { /* Skipped */ }
Все указанные подходы можно записать следующим образом:
- определяем функции выделяемой задачи или новой абстракции;
- согласуем их с существующими объектами в системе;
- разрабатываем интерфейс шаблона IFacade;
- реализуем IFacade в одном из вариантов:
- отдельный класс, переадресовывающего вызовы к объектам системы;
- в классе самой системы;
- клиент, вместо взаимодействия с несколькими классами, работает с одним новым интерфейсом.
Пример реализации
Рассмотрим пример создания простого редактора сообщений блога в рамках системы управления сайтом. Перечислим какие объекты будут входить в состав этой подсистемы. Авторизацию и прочие несущественные для примера методы уберем для краткости изложения.
Начнем с основного – записи в блог, представленной классом BlogPost. Этот класс хранит свойства и содержимое одной записи.
public class BlogPost
{
/// <summary>Gets or sets the post properties.</summary>
public BlogPostProperties Properties { get; set; }
/// <summary>Gets or sets the post content.</summary>
public BlogPostContent Content { get; set; }
}
Следующий класс BlogPostStorage – хранилище для записей блога на сервере. Он позволяет задавать данные для авторизации, загружать и публиковать записи.
public class BlogPostStorage
{
/// <summary>Gets or sets the server address.</summary>
public string Server { get; set; }
/// <summary>Gets or sets the blogger's login.</summary>
public string Login { get; set; }
/// <summary>Gets or sets the blogger's password.</summary>
public string Password { private get; set; }
/// <summary>Returns the list that represents the collection of post on server
/// for a specified period of time.</summary>
/// <param name="startDate">The start date.</param>
/// <param name="endDate">The end date.</param>
/// <returns>The list that represents the collection of post on server.</returns>
public List<BlogPostProperties> GetPostList(DateTime startDate, DateTime endDate)
{
/* Skipped */
}
/// <summary>Gets the post associated with the specified id.</summary>
/// <param name="postId">The id of the post to get.</param>
/// <returns>The instance for the id.</</returns>
public BlogPost GetPost(int postId) { /* Skipped */ }
/// <summary>Deletes the post associated with the specified id.</summary>
/// <param name="postId">The id of the post to delete.</param>
public void DeletePost(int postId) { /* Skipped */ }
/// <summary>Publishes the specified post.</summary>
/// <param name="post">The post to publish.</param>
public void Publish(BlogPost post) { /* Skipped */ }
}
Для проверки орфографии есть простой класс SimpleSpellChecker.
public class SimplepellChecker
{
/// <summary>Checks the word for spelling errors.</summary>
/// <param name="word">The word to check.</param>
/// <param name="suggestions">When this method returns, contains the suggestions
/// for the specified word.</param>
/// <returns>true on correct word; otherwise, false </returns>
public bool Check(string word, out List<string> suggestions) { /* Skipped */ }
}
И последний из компонентов – текстовый редактор. Уберем практически все его методы для краткости.
public class PostEditor
{
/// <summary>Spell checker. </summary>
private readonly SimpleSpellChecker _spellChecker = new SimpleSpellChecker();
/// <summary>Gets a value indicating whether spell check is complete.</summary>
public bool IsSpellCheckComplete { get; private set; }
/// <summary>Gets or sets the post to edit.</summary>
public BlogPost Post { get; set; }
/// <summary>Gets the post properties.</summary>
public BlogPostProperties PostProperties
{
get
{
if (this.Post == null) { return null; }
return this.Post.Properties;
}
}
/// <summary>Gets the post content.</summary>
public BlogPostContent PostContent
{
get
{
if (this.Post == null) { return null; }
return this.Post.Content;
}
}
}
Перейдем к редактору записей блога. Его подсистема будет выглядеть следующим образом:
public class BlogEditor
{
private readonly BlogPostStorage _storage = new BlogPostStorage();
public BlogPostStorage Storage { get { return this._storage; } }
private readonly PostEditor _editor = new PostEditor();
public PostEditor Editor { get { return this._editor; } }
}
Клиент может непосредственно использовать эти объекты. Но нужно ли ему это? Если посмотреть внимательно, то выяснится что необходимы следующие функции:
- указать реквизиты для входа на сервер: Server, Login, Password;
- загрузить список записей: GetPostList();
- загрузить запись для редактирования: LoadPost();
- получить свойства записи и ее текст: PostProperties, PostContent;
- убедиться что орфография записи проверена: IsSpellCheckComplete;
- опубликовать запись: Publish();
- удалить запись: DeletePost().
Все это можно записать в виде интерфейса для Фасада:
public interface IBlogEditorFacade
{
/// <summary>Gets or sets the server address.</summary>
string Server { get; set; }
/// <summary>Sets the blogger's login.</summary>
string Login { set; }
/// <summary>Sets the blogger's password.</summary>
string Password { set; }
/// <summary>Gets a value indicating whether spell check is complete.</summary>
bool IsSpellCheckComplete { get; }
/// <summary>Gets the current post properties.</summary>
BlogPostProperties PostProperties { get; }
/// <summary>Gets the current post content.</summary>
BlogPostContent PostContent { get; }
/// <summary>Returns the list that represents the collection of post on server
/// for a specified period of time.</summary>
/// <param name="startDate">The start date.</param>
/// <param name="endDate">The end date.</param>
/// <returns>The list that represents the collection of post on server.</returns>
List<BlogPostProperties> GetPostList(DateTime startDate, DateTime endDate);
/// <summary>Loads the specified post.</summary>
/// <param name="postId">The id of the post to get.</param>
void LoadPost(int postId);
/// <summary>Publishes the current post to blog.</summary>
void Publish();
/// <summary>Deletes the specified post.</summary>
/// <param name="postId">The id of the post to delete.</param>
void DeletePost(int postId);
}
Реализуем данный интерфейс. Созданные методы и свойства достаточно простые. Они просто переадресуют запрос к нужному объекту в системе. Поэтому сократим приведенный код для краткости. оставим только конструктор, и по одному свойству и методу.
public class BlogEditorFacade : IBlogEditorFacade
{
private BlogEditor _blogEditor;
/// <summary>Initializes a new instance of the
/// <see cref="BlogEditorFacade"/> class.</summary>
/// <param name="blogEditor">Reference to a blog editor instance.</param>
public BlogEditorFacade(BlogEditor blogEditor)
{
if (blogEditor == null) {
throw new ArgumentNullException("blogEditor can't be null");
}
this._blogEditor = blogEditor;
}
public string Server
{
get { return this._blogEditor.Storage.Server; }
set { this._blogEditor.Storage.Server = value; }
}
public void LoadPost(int postId)
{
BlogPost post = this._blogEditor.Storage.GetPost(postId);
if (post != null) {
this._blogEditor.Editor.Post = post;
}
}
/* IBlogEditorFacade implementation skipped */
}
В конструкторе класса указывается экземпляр системы, к которому Фасад должен обеспечить доступ. Теперь код клиента может использовать интерфейс для упрощения доступа:
public class BlogClient
{
private readonly IBlogEditorFacade _blogEditor = null;
public BlogClient(IBlogEditorFacade iBlogEditor)
{
this._blogEditor = iBlogEditor;
}
public bool ValidateAndPublish()
{
bool isPostValid = true;
BlogPostContent content = this._blogEditor.PostContent;
/// TODO: Validate the content
if (isPostValid) {
this._blogEditor.Publish();
}
return isPostValid;
}
}
Использование BlogClient выглядит следующим образом:
BlogEditor blogEditorObject = new BlogEditor();
/// ...
BlogClient blogClient = new BlogClient(new BlogEditorFacade(blogEditorObject));
Обратите внимание, что в конструктор Фасада передается интерфейс. Кроме того, создание объекта системы (BlogEditor) вынесено из реализации шаблона.
Таком образом клиент использует только интерфейс, который был для него создан, для работы с системой. Так же уменьшена зависимость реализации шаблона от реализации системы. Ведь вместо экземпляра BlogEditor можно передать любого его потомка.
Однако в некоторых случаях выгоднее создавать объект внутри самого шаблона. Например, чтобы каждый экземпляр Фасада имел свой экземпляр системы или чтобы, при необходимости, скрыть используемую систему от клиентов.
Возвращаясь к рассматриваемому примеру, можно отметить что, взаимодействие с системой стало проще. Количество методов и вызовов методов ее объектов уменьшилось. Цель применения шаблона достигнута.