facadeФасад (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, который через свои методы делегирует выполнение работы компонентам и их методам.

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

Реализация шаблона в общем виде

Возможны следующие подходы к реализации шаблона:

  1. Создание отдельного класса, который ссылается на экземпляр системы. Это классический вариант и реализация отчетливо выделена, т.к. является отдельным объектом.
    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 */
    }
  2. Добавление интерфейса Фасада в систему. Может пригодиться, если у Фасада много клиентов и нет смысла создавать каждому по собственному экземпляру реализации шаблона.
    public class AppSubSystem
    {
        public IFacade FacadeInterface  { get; private set; }
     
        public AppSubSystem ()
        {
            this.FacadeInterface = new FacadeImpl(this);
        }
     
        /* Skipped */
    }
  3. Поддержка интерфейса Фасада в классе самой системы, используя наследование. Реализация получается “размытой” по системе. Однако, теперь ее экземпляр может использоваться там, где требуется интерфейс Фасада. Кроме того, появляется возможность доступа к закрытым полям и методам системы, а так же нет необходимости переадресации вызовов одинаковых методов.
    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 можно передать любого его потомка.

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

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