PHP: Фасад

Facade Фасад Facade

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

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

Подробней о Фасаде

Особенности паттерна на PHP

Сложность:

Популярность:

Применимость: Паттерн часто встречается в клиентских приложениях, написанных на PHP, которые используют классы-фасады для упрощения работы со сложными библиотеки или API.

Признаки применения паттерна: Фасад угадывается в классе, который имеет простой интерфейс, но делегирует основную часть работы другим классам. Чаще всего, фасады сами следят за жизненным циклом объектов сложной системы.

Пример: Структура паттерна

Этот пример показывает структуру паттерна Фасад, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом. После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.

FacadeStructural.php: Пример структуры паттерна

<?php

namespace RefactoringGuru\Facade\Structural;

/**
 * Класс Фасада предоставляет простой интерфейс для сложной логики одной или
 * нескольких подсистем. Фасад делегирует запросы клиентов соответствующим
 * объектам внутри подсистемы. Фасад также отвечает за управление их жизненным
 * циклом. Все это защищает клиента от нежелательной сложности подсистемы.
 */
class Facade
{
    protected $subsystem1;

    protected $subsystem2;

    /**
     * В зависимости от потребностей вашего приложения вы можете предоставить
     * Фасаду существующие объекты подсистемы или заставить Фасад создать их
     * самостоятельно.
     */
    public function __construct(
        Subsystem1 $subsystem1 = null,
        Subsystem2 $subsystem2 = null
    ) {
        $this->subsystem1 = $subsystem1 ?: new Subsystem1();
        $this->subsystem2 = $subsystem2 ?: new Subsystem2();
    }

    /**
     * Методы Фасада удобны для быстрого доступа к сложной функциональности
     * подсистем. Однако клиенты получают только часть возможностей подсистемы.
     */
    public function operation()
    {
        $result = "Facade initializes subsystems:\n";
        $result .= $this->subsystem1->operation1();
        $result .= $this->subsystem2->operation1();
        $result .= "Facade orders subsystems to perform the action:\n";
        $result .= $this->subsystem1->operationN();
        $result .= $this->subsystem2->operationZ();

        return $result;
    }
}

/**
 * Подсистема может принимать запросы либо от фасада, либо от клиента напрямую.
 * В любом случае, для Подсистемы Фасад – это еще один клиент,  и он не является
 * частью Подсистемы.
 */
class Subsystem1
{
    function operation1()
    {
        return "Subsystem1: Ready!\n";
    }

    // ...

    function operationN()
    {
        return "Subsystem1: Go!\n";
    }
}

/**
 * Некоторые фасады могут работать с разными подсистемами одновременно.
 */
class Subsystem2
{
    public function operation1()
    {
        return "Subsystem2: Get ready!\n";
    }

    // ...

    public function operationZ()
    {
        return "Subsystem2: Fire!\n";
    }
}

/**
 * Клиентский код работает со сложными подсистемами через простой интерфейс,
 * предоставляемый Фасадом. Когда фасад управляет жизненным циклом подсистемы,
 * клиент может даже не знать о существовании подсистемы. Такой подход позволяет
 * держать сложность под контролем.
 */
function clientCode(Facade $facade)
{
    // ...

    print($facade->operation());

    // ...
}

/**
 * В клиентском коде могут быть уже созданы некоторые объекты подсистемы. В этом
 * случае может оказаться целесообразным инициализировать Фасад с этими
 * объектами вместо того, чтобы позволить Фасаду создавать новые экземпляры.
 */
$subsystem1 = new Subsystem1();
$subsystem2 = new Subsystem2();
$facade = new Facade($subsystem1, $subsystem2);
clientCode($facade);

Output.txt: Результат выполнения

Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!

Пример: Пример из жизни

Думайте о Фасаде как о «адаптере-упрощателе» для некой сложной подсистемы. Фасад изолирует сложность в рамках одного класса и позволяет остальному коду приложения использовать простой интерфейс.

В этом примере Фасад скрывает сложность API YouTube и библиотеки FFmpeg от клиентского кода. Вместо того, чтобы работать с десятками классов, клиент использует простой метод Фасада.

FacadeRealWorld.php: Пример из жизни

<?php

namespace RefactoringGuru\Facade\RealWorld;

/**
 * Паттерн Фасад
 *
 * Назначение: Предоставляет единый интерфейс к ряду интерфейсов в подсистеме.
 * Фасад определяет интерфейс более высокого уровня, который упрощает
 * использование подсистемы.
 *
 * Пример: Думайте о Фасаде как о «адаптере-упрощателе» для некой сложной
 * подсистемы. Фасад изолирует сложность в рамках одного класса и позволяет
 * остальному коду приложения использовать простой интерфейс.
 *
 * В этом примере Фасад скрывает сложность API YouTube и библиотеки FFmpeg от
 * клиентского кода. Вместо того, чтобы работать с десятками классов, клиент
 * использует простой метод Фасада.
 */

/**
 * Фасад предоставляет единый метод загрузки видео с YouTube. Этот метод
 * скрывает всю сложность сетевого уровня PHP, API YouTube и библиотеки
 * преобразования видео (FFmpeg).
 */
class YouTubeDownloader
{
    protected $youtube;
    protected $ffmpeg;

    /**
     * Бывает удобным сделать Фасад ответственным за управление жизненным циклом
     * используемой подсистемы.
     */
    public function __construct(string $youtubeApiKey)
    {
        $this->youtube = new YouTube($youtubeApiKey);
        $this->ffmpeg = new FFMpeg();
    }

    /**
     * Фасад предоставляет простой метод загрузки видео и кодирования его в
     * целевой формат (для простоты понимания примера реальный код
     * закомментирован).
     */
    public function downloadVideo(string $url)
    {
        print("Fetching video metadata from youtube...\n");
        // $title = $this->youtube->fetchVideo($url)->getTitle();
        print("Saving video file to a temporary file...\n");
        // $this->youtube->saveAs($url, "video.mpg");

        print("Processing source video...\n");
        // $video = $this->ffmpeg->open('video.mpg');
        print("Normalizing and resizing the video to smaller dimensions...\n");
        // $video
        //     ->filters()
        //     ->resize(new FFMpeg\Coordinate\Dimension(320, 240))
        //     ->synchronize();
        print("Capturing preview image...\n");
        // $video
        //     ->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(10))
        //     ->save($title . 'frame.jpg');
        print("Saving video in target formats...\n");
        // $video
        //     ->save(new FFMpeg\Format\Video\X264(), $title . '.mp4')
        //     ->save(new FFMpeg\Format\Video\WMV(), $title . '.wmv')
        //     ->save(new FFMpeg\Format\Video\WebM(), $title . '.webm');
        print("Done!\n");
    }
}

/**
 * Подсистема API YouTube.
 */
class YouTube
{
    function fetchVideo() { /* ... */ }

    function saveAs($path) { /* ... */ }

    // ...more methods and classes... RU: ...дополнительные методы и классы...
}

/**
 * Подсистема FFmpeg (сложная библиотека работы с видео/аудио).
 */
class FFMpeg
{
    static public function create() { /* ... */ }

    public function open(string $video) { /* ... */ }

    // ...more methods and classes... RU: ...дополнительные методы и классы...
}

class FFMpegVideo
{
    public function filters() { /* ... */ }

    public function resize() { /* ... */ }

    public function synchronize() { /* ... */ }

    public function frame() { /* ... */ }

    public function save(string $path) { /* ... */ }

    // ...more methods and classes... RU: ...дополнительные методы и классы...
}


/**
 * Клиентский код не зависит от классов подсистем. Любые изменения внутри кода
 * подсистем не будут влиять на клиентский код. Вам нужно будет всего лишь
 * обновить Фасад.
 */
function clientCode(YouTubeDownloader $facade)
{
    // ...

    $facade->downloadVideo("https://www.youtube.com/watch?v=QH2-TGUlwu4");

    // ...
}

$facade = new YouTubeDownloader("APIKEY-XXXXXXXXX");
clientCode($facade);

Output.txt: Результат выполнения

Fetching video metadata from youtube...
Saving video file to a temporary file...
Processing source video...
Normalizing and resizing the video to smaller dimensions...
Capturing preview image...
Saving video in target formats...
Done!