Фасад

Фасад


Дмитрий Бахтенков

Что это такое?

Фасад - это структурный паттерн проектирования, который предоставляет простой интерфейс к более сложной системе классов.

Когда использовать?

  • Когда имеется сложный модуль (или несколько модулей) в системе,и необходимо упростить с ним работу - фасад позволяет определить единую точку взаимодействия между клиентом и модулем.
  • Когда необходимо уменьшить кол-во зависимостей между клиентом и сложным модулем - фасад "прячет" все зависимости внутри, оставляя только простой интерфейс взаимодействия

Концепт

На диаграмме UML фасад будет выглядеть примерно следующим образом:

Facade UML

Здесь модули - это различные классы, с которыми должна взаимодействовать наша система. Там может быть любое кол-во операций, полей. Класс Facade содержит в себе эти модули - они связаны отношением ассоциации. Модули можно инициализировать как где-то вовне фасада - тогда это будет агрегация, или внутри - композиция. Если вы ничего не поняли, советую вам прочитать статью "Отношения между классами и объектами" (Vk || Telegram).

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

//какие-то внутренние модули
public class Module1
{
    public void OperationA()
    {
        Console.WriteLine("operation a");
    }
}

public class Module2
{
    public void OperationB()
    {
        Console.WriteLine("operation b");
    }
}

public class Module3
{
    public void OperationC()
    {
        Console.WriteLine("operation c");
    }
}

Далее опишем фасад. Он может быть как в простом варианте, то есть предоставлять доступ к классам модулей напрямую через свойства:

public class SimpleFacade
{
    // модули, которые скрывает в себе фасад
    public Module1 Module1 { get; }
    public Module2 Module2 { get; }
    public Module3 Module3 { get; }


    public SimpleFacade()
    {
        // инициализация модулей
        Module1 = new Module1();
        Module2 = new Module2();
        Module3 = new Module3();
    }
}

так и в более строгом варианте, то есть содержать в себе готовые операции, использующие модуль определённым способом:

public class Facade
{
    // модули, которые скрывает в себе фасад
    private Module1 _module1;
    private Module2 _module2;
    private Module3 _module3;

    public Facade()
    {
        // инициализация модулей
        _module1 = new Module1();
        _module2 = new Module2();
        _module3 = new Module3();
    }

    // первая операция, которая вызывает в нужном порядке внутренние методы
    public void FacadeOperationA()
    {
        _module1.OperationA();
        _module3.OperationC();
    }

    // вторая операция, которая вызывает в нужном порядке внутренние методы
    public void FacadeOperationB()
    {
        _module3.OperationC();
        _module2.OperationB();
        _module1.OperationA();
    }
}

Используем наш фасад в классе Program:

class Program
{
    static void Main(string[] args)
    {
        var facade = new Example.Facade();
        Console.WriteLine();
        facade.FacadeOperationA();
        Console.WriteLine();
        facade.FacadeOperationB();
    }
}

Вывод:

Пример

Допустим, мы пишем интернет-магазин, где можно выбрать набор товаров, заказать их с доставкой и оплатить онлайн. Также возможен самовывоз и оплата наличными. При заказе клиент передаёт список товаров, адрес, тип оплаты и данные своей карты, а системе нужно сделать следующее:

  1. Получить массив выбранных товаров и их общую сумму
  2. Проверить товары на наличие на складе
  3. Для оплаты товаров воспользоваться сервисом платежей
  4. Создать заявку на доставку товаров
  5. Отправить клиенту уведомление на email об успешном заказе

При оплате офлайн вместо использования платёжного сервиса генерируется дополнительный счёт, который также отправляется на email, то есть порядок действий такой:

  1. Получить массив выбранных товаров и их общую сумму
  2. Проверить товары на наличие на складе
  3. Сгенерировать счёт
  4. Создать заявку на доставку товаров
  5. Отправить клиенту счёт
  6. Отправить клиенту уведомление на email об успешном заказе

Проектирование

В этот раз кода будет немного больше, чем обычно, поэтому подробнее остановимся и на проектировании. В первую очередь нам надо описать нашего клиента:

И продукт:

Затем опишем необходимые сервисы, или модули. Это классы, которые содержат в себе некий функционал и используются при заказе:

И сам фасад:

Полностью UML-диаграмма выглядит следующим образом:

Код

Определим модели продукта и клиента:

public class Product
{
    public string Name { get; set; }
    public int Cost { get; set; } 
}

public class Client
{
    public string Email { get; set; }
    public string CardNumber { get; set; }
    public DateTime CardDate { get; set; }
    public int Cvc { get; set; }
    public string Address { get; set; }
}

Далее определим сервисы. В реальных проектах они могут быть более сложны, для простоты воспользуемся "заглушками".

Сервис склада для проверки наличия продуктов:

public class WarehouseService
{
    public bool CheckProducts(Product[] products)
    {
        // тут должна быть логика проверки товаров на складе
        Console.WriteLine("Все продукты в наличии");
        return true;
    }
}

Платёжный сервис:

public class PaymentService
{
    public void ExecutePayment(string number, DateTime date, int cvc, int cost)
    {
        // логика взаимодействия с платёжным сервисом
        Console.WriteLine($"Выполнен платёж на сумму {cost} руб.");
    }

    public void CreateCheque(int cost)
    {
        // логика генерации чека на офлайн оплату
        Console.WriteLine($"Чек для заказа на сумму {cost} руб.");
    }
}

Сервис доставки:

public class DeliveryService
{
    public void CreateDelivery(string address, Product[] products)
    {
        // логика создания заявки на доставку выбранных продуктов
        Console.WriteLine("Заявка на доставку успешно зарегистрирована");
    }
}

Сервис email:

public class EmailService
{
    public void SendEmail(string email, string text)
    {
        //логика отправки письма
        Console.WriteLine($"Сообщение отправлено на почту {email}");
     Console.WriteLine(text);
    }
}

Теперь нужно написать фасад:

public class OrderFacade
{
    // набор сервисов, которые надо "обернуть в фасад"
    private WarehouseService _warehouse;
    private PaymentService _payment;
    private DeliveryService _delivery;
    private EmailService _email;

    public OrderFacade()
    {
        // инициализируем зависимости в конструкторе класса
        _warehouse = new WarehouseService();
        _payment = new PaymentService();
        _delivery = new DeliveryService();
        _email = new EmailService();
    }

    public void CreateOfflineOrder(Client client, Product[] products)
    {
        // проверяем наличие товаров, и если их нет завершаем обработку заказа
        if (!_warehouse.CheckProducts(products))
        {
            Console.WriteLine("Товаров нет на складе");
            return;
        }

        // получаем общую цену всех продуктов
        var cost = products.Sum(x => x.Cost);
        
        // генерируем счёт
        _payment.CreateCheque(cost);
        
        // создаём доставку
        _delivery.CreateDelivery(client.Address, products);
        
        _email.SendEmail(client.Email, $"Ваш счёт: {cost} руб.");
        _email.SendEmail(client.Email, "Ваш заказ  создан");
    }

    public void CreateOnlineOrder(Client client, Product[] products)
    {
        // проверяем наличие товаров, и если их нет завершаем обработку заказа
        if (!_warehouse.CheckProducts(products))
        {
            Console.WriteLine("Товаров нет на складе");
            return;
        }

        // получаем общую цену всех продуктов
        var cost = products.Sum(x => x.Cost);
        
        // выполняем платёж 
        _payment.ExecutePayment(client.CardNumber, client.CardDate, client.Cvc, cost);
        
        // создаём доставку
        _delivery.CreateDelivery(client.Address, products);
        
        _email.SendEmail(client.Email, "Ваш заказ  создан");
    }
}

Воспользуемся им в классе Program:

static void Main(string[] args)
{
    var facade = new OrderFacade();
    var client = new Client
    {
        Email = "a@a.a",
        Cvc = 124,
        Address = "ул. пушкина дом колотушкина",
        CardDate = DateTime.Now,
        CardNumber = "1234 1234 1234 1234"
    };

    var products = new[]
    {
        new Product {Name = "Продукт", Cost = 1000}
    };
    
    facade.CreateOnlineOrder(client, products);
}

Вывод приложения:

Делаем выводы

Фасад, по своей сути, является обёрткой для большого кол-ва модулей или классов. В реальных проектах он применяется достаточно часто. Его целью является скрытие большого кол-ва зависимостей и предоставление удобного контракта взаимодействия для других частей системы.

______________

С вами был Flex Code! Исходный код можно посмотреть тут

Vk | Telegram

Report Page