- Суть паттерна
Фасад - это структурный паттерн проектирования, который предоставляет простой интерфейс к сложной системе классов, библиотеке или фреймворку.
- Проблема
Вашему коду приходится работать с большим количеством объектов некой сложной библиотеки или фреймворка. Вы должны самостоятельно инициализировать эти объекты, следить за правильным порядком зависимостей и так далее.
В результате бизнес-логика ваших классов тесно переплетается с деталями реализации сторонних классов. Такой код довольно сложно понимать и поддерживать.
- Решение
Фасад - это простой интерфейс для работы со сложной подсистемой, содержащей множество классов. Фасад может иметь урезанный интерфейс, не имеющий 100% функциональности, которой можно достичь, используя сложную подсистему напрямую. Но он предоставляет именно те фичи, которые нужны клиенту, и скрывает все остальные.
Фасад полезен, если вы используете какую-то сложную библиотеку со множеством подвижных частей, но вам нужна только часть её возможностей.
К примеру, программа, заливающая видео котиков в социальные сети, может использовать профессиональную библиотеку сжатия видео. Но все, что нужно клиентскому коду этой программы - простой метод encode(filename, format). Создав класс с таким методом, вы реализуете свой первый фасад.
- Структура
- Шаги реализации
- Преимущества
- Недостатки
Facade - это структурный паттерн, который предоставляет простой (но урезанный) интерфейс к сложной системе объектов, библиотеке или фреймворку. Кроме того, что Фасад позволяет снизить общую сложность программы, он также помогает вынести код, зависимый от внешней системы в единственное место.
Концептуальный пример
/** * Класс Фасада предоставляет простой интерфейс для сложной логики одной или * нескольких подсистем. Фасад делегирует запросы клиентов соответствующим * объектам внутри подсистемы. Фасад также отвечает за управление их жизненным * циклом. Все это защищает клиента от нежелательной сложности подсистемы. */ class Facade { protected $subsystem1; protected $subsystem2; /** * В зависимости от потребностей вашего приложения вы можете предоставить * Фасаду существующие объекты подсистемы или заставить Фасад создать их самостоятельно. */ public function __construct($subsystem1 = null, $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 { public function operation1() { return "Subsystem1: Ready!\n"; } // ... public 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) { // ... echo $facade->operation(); // ... } /** * В клиентском коде могут быть уже созданы некоторые объекты подсистемы. В этом * случае может оказаться целесообразным инициализировать Фасад с этими * объектами вместо того, чтобы позволить Фасаду создавать новые экземпляры. */ $subsystem1 = new Subsystem1(); $subsystem2 = new Subsystem2(); $facade = new Facade($subsystem1, $subsystem2); clientCode($facade);
Результат выполнения:
Facade initializes subsystems: Subsystem1: Ready! Subsystem2: Get ready! Facade orders subsystems to perform the action: Subsystem1: Go! Subsystem2: Fire!
Пример из реальной жизни
В этом примере Фасад скрывает сложность API YouTube и библиотеки FFmpeg от клиентского кода. Вместо того, чтобы работать с десятками классов, клиент использует простой метод Фасада.
/** * Фасад предоставляет единый метод загрузки видео с YouTube. Этот метод * скрывает всю сложность сетевого уровня PHP, API YouTube и библиотеки * преобразования видео (FFmpeg). */ class YouTubeDownloader { protected $youtube; protected $ffmpeg; /** * Бывает удобным сделать Фасад ответственным за управление жизненным циклом * используемой подсистемы. */ public function __construct($youtubeApiKey) { $this->youtube = new YouTube($youtubeApiKey); $this->ffmpeg = new FFMpeg(); } /** * Фасад предоставляет простой метод загрузки видео и кодирования его в * целевой формат (для простоты понимания примера реальный код закомментирован). */ public function downloadVideo($url) { echo "Fetching video metadata from youtube...\n"; // $title = $this->youtube->fetchVideo($url)->getTitle(); echo "Saving video file to a temporary file...\n"; // $this->youtube->saveAs($url, "video.mpg"); echo "Processing source video...\n"; // $video = $this->ffmpeg->open('video.mpg'); echo "Normalizing and resizing the video to smaller dimensions...\n"; // $video // ->filters() // ->resize(new FFMpeg\Coordinate\Dimension(320, 240)) // ->synchronize(); echo "Capturing preview image...\n"; // $video // ->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(10)) // ->save($title . 'frame.jpg'); echo "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'); echo "Done!\n"; } } /** * Подсистема API YouTube. */ class YouTube { public function fetchVideo() { /* ... */ } public function saveAs($path) { /* ... */ } // ...дополнительные методы и классы... } /** * Подсистема FFmpeg (сложная библиотека работы с видео/аудио). */ class FFMpeg { public static function create() { /* ... */ } public function open($video) { /* ... */ } // ...дополнительные методы и классы... } class FFMpegVideo { public function filters() { /* ... */ } public function resize() { /* ... */ } public function synchronize() { /* ... */ } public function frame() { /* ... */ } public function save($path) { /* ... */ } // ...дополнительные методы и классы... } /** * Клиентский код не зависит от классов подсистем. Любые изменения внутри кода * подсистем не будут влиять на клиентский код. Вам нужно будет всего лишь обновить Фасад. */ function clientCode($facade) { // ... $facade->downloadVideo("https://www.youtube.com/watch?v=S5S9LIT-hdc"); // ... } $facade = new YouTubeDownloader("APIKEY-XXXXXXXXX"); clientCode($facade);
Результат выполнения:
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!