Перейти к содержанию

Facade (Фасад)

Категория: структурный паттерн.

Проблема

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

Решение

Вводится класс-фасад, который: 1. Знает о всех классах подсистемы, необходимых для типового сценария. 2. Предоставляет один (или несколько) простых метода, реализующих этот сценарий целиком. 3. Прячет внутри себя порядок вызовов, обработку промежуточных результатов и детали взаимодействия компонентов подсистемы.

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

Структура

  • Subsystem classes - набор классов со своей логикой и взаимными зависимостями.
  • Facade - единая точка входа с упрощёнными методами для типовых сценариев.
  • Client - в большинстве случаев работает через Facade, но при необходимости может обратиться к классам подсистемы напрямую.

Когда применять

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

Плюсы

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

Минусы

  • Фасад рискует превратиться в "божественный объект" (God Object), если в него сваливают всё больше несвязанной логики.
  • Не решает проблему сложности - лишь прячет её за более удобным интерфейсом; сама подсистема продолжает быть сложной.

Отличие от Adapter

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

Пример в .NET Framework / BCL

  • HttpClient - фасад над низкоуровневой работой с сокетами, DNS, TLS-рукопожатием и т.д.
  • File/Directory в System.IO - фасад над более низкоуровневыми FileStream, FileSystemInfo и системными вызовами.
  • Классы вида *Service/*Manager в бизнес-логике приложений, скрывающие за собой работу с несколькими репозиториями и внешними клиентами.

Пример реализации на C#

Facade.cs
using System;

namespace DesignPatterns.Structural.Facade
{
    // Сложная подсистема из нескольких взаимосвязанных классов.
    // По отдельности каждый класс делает свою узкую задачу и требует знания
    // о порядке вызовов и о том, как передавать данные между ними.

    public sealed class InventoryService
    {
        public bool Reserve(string sku, int quantity)
        {
            Console.WriteLine($"Резервируем {quantity} шт. товара {sku}");
            return true;
        }
    }

    public sealed class PaymentService
    {
        public bool Charge(string customerId, decimal amount)
        {
            Console.WriteLine($"Списываем {amount:C} с клиента {customerId}");
            return true;
        }
    }

    public sealed class ShippingService
    {
        public string ScheduleDelivery(string customerId, string sku)
        {
            Console.WriteLine($"Планируем доставку {sku} клиенту {customerId}");
            return "TRACK-12345";
        }
    }

    public sealed class NotificationService
    {
        public void NotifyOrderCreated(string customerId, string trackingNumber)
        {
            Console.WriteLine($"Отправляем клиенту {customerId} письмо с трек-номером {trackingNumber}");
        }
    }

    // Facade даёт один простой метод для типового сценария "оформить заказ",
    // пряча за собой всю последовательность вызовов подсистемы.
    public sealed class OrderFacade
    {
        private readonly InventoryService _inventory = new();
        private readonly PaymentService _payment = new();
        private readonly ShippingService _shipping = new();
        private readonly NotificationService _notification = new();

        public void PlaceOrder(string customerId, string sku, int quantity, decimal amount)
        {
            if (!_inventory.Reserve(sku, quantity))
            {
                throw new InvalidOperationException("Товара нет в наличии");
            }

            _payment.Charge(customerId, amount);
            string trackingNumber = _shipping.ScheduleDelivery(customerId, sku);
            _notification.NotifyOrderCreated(customerId, trackingNumber);
        }
    }

    public static class Demo
    {
        public static void Run()
        {
            // Клиентскому коду не нужно знать про четыре сервиса и порядок их вызова.
            var facade = new OrderFacade();
            facade.PlaceOrder(customerId: "C-1", sku: "SKU-42", quantity: 1, amount: 19.90m);
        }
    }
}

Открыть Facade.cs отдельно Скачать Facade.cs