<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
АРХІТЕКТУРА · MIDDLE ЧАСТО ПИТАЮТЬ

Adapter і Facade: у чому різниця і чому фасади Laravel не є фасадом з GoF?

Адаптер підганяє один чужий обʼєкт під інтерфейс, якого вже чекає ваш код, і поведінки не змінює. Фасад вводить новий, простіший вхід до кількох сервісів підсистеми й сам знає порядок їх виклику. Фасади Laravel (`Cache::`, `Mail::`) - це статичні проксі до одного сервісу з контейнера, підсистеми за ними немає, тож з GoF Facade їх повʼязує лише назва.

Чим адаптер відрізняється від фасаду? І те, і те ж обгортка.
`Cache::get()` у Laravel - це патерн Facade?
Ми підключаємо LiqPay і monobank. Що ви напишете: адаптер, фасад чи обидва?
У нас є `PaymentFacade` на сорок методів, і його імпортує пів проєкту. Що з ним не так?
design patterns Adapter Facade фасади Laravel порти й адаптери інтеграції

Адаптер і фасад обидва стоять між вашим кодом і чимось складнішим, тому їх легко сплутати. Відрізняє їх напрям інтерфейсу. Адаптер починається з інтерфейсу, який у вас уже є (PaymentGateway::charge(string $orderId, int $kopecks)), і змушує чужий SDK під нього підлаштуватися: перекладає копійки в гривні, масив відповіді в рядок ідентифікатора, коди помилок у ваш PaymentDeclined. Фасад нічого не підганяє: він вводить новий, вужчий вхід до кількох власних сервісів і бере на себе знання про порядок кроків.

У проєкті з двома платіжками доречні обидва. Кожен провайдер отримує свій адаптер за спільним портом, і сценарій оплати не знає, з ким говорить. Якщо оплату супроводжують фіскалізація чеку й повідомлення бухгалтерії, їх можна сховати за фасадом, який викликають сценарії. Важливо, щоб адаптер справді ізолював постачальника: обʼєкти SDK і його винятки не повинні виходити назовні, інакше заміна провайдера зачепить усі місця виклику так само, як без адаптера.

Фасади Laravel взяли назву, але не намір. Cache::get() нічого не обʼєднує: Facade::__callStatic() дістає з контейнера сервіс за ключем з getFacadeAccessor() і викликає на ньому звичайний метод. Це статичний проксі до одного обʼєкта, і саме тому працюють Cache::shouldReceive(), Mail::fake() і Queue::fake(): вони підміняють обʼєкт, до якого фасад веде. У Symfony статичних фасадів немає зовсім, там фасад GoF - звичайний сервіс з автовайрингом, як і в Laravel, коли ви пишете його самі.

Обидва патерни мають межу корисності. Адаптер навколо зручного SDK, який використовується в одному місці й міняти який ніхто не планує, лише додає клас. Фасад над одним сервісом, що переказує його методи, нічого не ховає. А фасад, у який дописують усе повʼязане з темою, перетворюється на God object з десятками залежностей: тримайте його на одному сценарії або вузькій групі, а коли сценаріїв багато, діліть на кілька фасадів чи сервісів застосунку.

// Порт: інтерфейс, який ваш код описує своєю мовою
interface PaymentGateway
{
    /** @return string ідентифікатор платежу в провайдера */
    public function charge(string $orderId, int $kopecks): string;
}

// Adapter: перекладає ваш виклик на чужий клієнт і назад, поведінку не змінює
final class LiqPayGateway implements PaymentGateway
{
    public function __construct(private LiqPay $sdk) {} // офіційний SDK

    public function charge(string $orderId, int $kopecks): string
    {
        // api() повертає stdClass з json_decode(); параметри скорочено
        $response = $this->sdk->api('request', [
            'action' => 'pay',
            'version' => 3,
            'order_id' => $orderId,
            'amount' => $kopecks / 100, // API чекає гривні, ваш код рахує в копійках
            'currency' => 'UAH',
        ]);

        // Формат відповіді й помилки постачальника не виходять за межі адаптера
        if (($response->status ?? null) !== 'success') {
            throw new PaymentDeclined($response->err_description ?? 'unknown');
        }

        return (string) $response->payment_id;
    }
}

// Facade: новий простий вхід, за яким кілька сервісів і порядок їх виклику
final class Shipping
{
    public function __construct(
        private Carrier $carrier,
        private LabelPrinter $printer,
        private Tracking $tracking,
    ) {}

    public function send(Order $order): string
    {
        $waybill = $this->carrier->createWaybill($order);
        $this->printer->print($waybill);
        $this->tracking->watch($waybill, $order);

        return $waybill;
    }
}
Що кандидат бачить напрям інтерфейсу: в адаптера інтерфейс належить клієнту (ваш порт), фасад створює новий інтерфейс спеціально для клієнтів підсистеми.
Що адаптер обгортає один чужий обʼєкт і перекладає дані й помилки, а фасад координує кілька сервісів і порядок кроків.
Що він пояснює механіку Laravel-фасаду: `__callStatic()`, ключ з `getFacadeAccessor()`, обʼєкт з контейнера, тому `fake()` і `shouldReceive()` працюють.
Що адаптер у гексагональній архітектурі - це реалізація порту в інфраструктурі, і в тестах її замінює in-memory фейк.
Що фасад не забороняє прямий доступ до підсистеми, і що він легко розростається в God object.
Називати адаптером обгортку, яка дослівно повторює SDK (`LiqPayAdapter::api(string $path, array $params)`): формат постачальника все одно розходиться по коду, а клас лише додає стрибок у навігації.
Повертати з адаптера обʼєкти SDK або пропускати його винятки назовні: клієнтський код знову залежить від постачальника, і заміна LiqPay на monobank зачіпає всі місця виклику.
Казати, що `Cache::get()` неможливо підмінити в тесті, бо виклик статичний: фасад резолвить обʼєкт з контейнера, і `Cache::shouldReceive()` чи `Mail::fake()` підміняють саме його.
Вважати `Cache::` фасадом GoF, бо так він називається в документації: за ним стоїть один сервіс `cache`, і нічого не обʼєднано.
Дописувати у фасад усе, що стосується теми (тарифи, трекінг, повернення, звіти), поки він не стане вхідною точкою цілого модуля з десятками залежностей.
ПОРАДА

Розведіть патерни двома питаннями: чий інтерфейс (ваш готовий чи новий) і скільки обʼєктів за обгорткою (один чужий чи кілька своїх). Потім окремо скажіть про Laravel: `Cache::` - це статичний доступ до сервісу з контейнера, а фасад у сенсі GoF ви пишете звичайним класом і інжектуєте.

оновлено 8 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Адаптер перекладає чужий API на ваш готовий інтерфейс і нічого не додає до поведінки. Фасад створює новий інтерфейс для клієнтів і ховає за ним кілька сервісів та порядок їх виклику. Кеш і логування поверх того самого інтерфейсу - це декоратор, а `Cache::` у Laravel - статичний проксі до одного сервісу з контейнера.