Facade を PHP で
Facade は、 構造に関するデザインパターンの一つで、 複雑なクラスのシステム、 ライブラリー、 またはフレームワークに対して単純な (しかし限定された) インターフェースを提供します。
Facade は、 アプリケーションの全体としての複雑さを軽減しますが、 それと同時に望ましくない依存性を一箇所に集めるのにも役立ちます。
複雑度:
人気度:
使用例: Facade パターンは、 PHP のアプリケーションでよく見かけ、 複雑なライブラリーや API を相手にする作業を簡素化しています。
見つけ方: 単純なインターフェースのクラスがほとんどの作業を他のクラスに委任していたら、 Facade パターンの使用が識別できます。 通常、 ファサードは、 それが使うオブジェクトのライフサイクルを完全に管理します。
概念的な例
この例は、 Facade デザインパターンの構造を説明するためのものです。 以下の質問に答えることを目的としています:
- どういうクラスからできているか?
- それぞれのクラスの役割は?
- パターンの要素同士はどう関係しているのか?
ここでパターンの構造を学んだ後だと、 これに続く、 現実世界の PHP でのユースケースが理解しやすくなります。
index.php: 概念的な例
<?php namespace RefactoringGuru\Facade\Conceptual; /** * The Facade class provides a simple interface to the complex logic of one or * several subsystems. The Facade delegates the client requests to the * appropriate objects within the subsystem. The Facade is also responsible for * managing their lifecycle. All of this shields the client from the undesired * complexity of the subsystem. */ class Facade { protected $subsystem1; protected $subsystem2; /** * Depending on your application's needs, you can provide the Facade with * existing subsystem objects or force the Facade to create them on its own. */ public function __construct( Subsystem1 $subsystem1 = null, Subsystem2 $subsystem2 = null ) { $this->subsystem1 = $subsystem1 ?: new Subsystem1(); $this->subsystem2 = $subsystem2 ?: new Subsystem2(); } /** * The Facade's methods are convenient shortcuts to the sophisticated * functionality of the subsystems. However, clients get only to a fraction * of a subsystem's capabilities. */ public function operation(): string { $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; } } /** * The Subsystem can accept requests either from the facade or client directly. * In any case, to the Subsystem, the Facade is yet another client, and it's not * a part of the Subsystem. */ class Subsystem1 { public function operation1(): string { return "Subsystem1: Ready!\n"; } // ... public function operationN(): string { return "Subsystem1: Go!\n"; } } /** * Some facades can work with multiple subsystems at the same time. */ class Subsystem2 { public function operation1(): string { return "Subsystem2: Get ready!\n"; } // ... public function operationZ(): string { return "Subsystem2: Fire!\n"; } } /** * The client code works with complex subsystems through a simple interface * provided by the Facade. When a facade manages the lifecycle of the subsystem, * the client might not even know about the existence of the subsystem. This * approach lets you keep the complexity under control. */ function clientCode(Facade $facade) { // ... echo $facade->operation(); // ... } /** * The client code may have some of the subsystem's objects already created. In * this case, it might be worthwhile to initialize the Facade with these objects * instead of letting the Facade create new instances. */ $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! 現実的な例
Facade を、 複雑なサブシステムに対する単純化アダプターと考えてみてください。 Facade は、 複雑さを一つのクラスに隔離することにより、 アプリケーション・コードが分かりやすいインターフェースを使えるようにします。
この例では、 ファサードが、 YoutTube API と FFmpeg ライブラリーの複雑さをクライアント・コードから隔離します。 数十ものクラスと関わる代わりに、 クライアントはファサード上の単純なメソッド一つを使用します。
index.php: 現実的な例
<?php namespace RefactoringGuru\Facade\RealWorld; /** * The Facade provides a single method for downloading videos from YouTube. This * method hides all the complexity of the PHP network layer, YouTube API and the * video conversion library (FFmpeg). */ class YouTubeDownloader { protected $youtube; protected $ffmpeg; /** * It is handy when the Facade can manage the lifecycle of the subsystem it * uses. */ public function __construct(string $youtubeApiKey) { $this->youtube = new YouTube($youtubeApiKey); $this->ffmpeg = new FFMpeg(); } /** * The Facade provides a simple method for downloading video and encoding it * to a target format (for the sake of simplicity, the real-world code is * commented-out). */ public function downloadVideo(string $url): void { 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"; } } /** * The YouTube API subsystem. */ class YouTube { public function fetchVideo(): string { /* ... */ } public function saveAs(string $path): void { /* ... */ } // ...more methods and classes... } /** * The FFmpeg subsystem (a complex video/audio conversion library). */ class FFMpeg { public static function create(): FFMpeg { /* ... */ } public function open(string $video): void { /* ... */ } // ...more methods and classes... RU: ...дополнительные методы и классы... } class FFMpegVideo { public function filters(): self { /* ... */ } public function resize(): self { /* ... */ } public function synchronize(): self { /* ... */ } public function frame(): self { /* ... */ } public function save(string $path): self { /* ... */ } // ...more methods and classes... RU: ...дополнительные методы и классы... } /** * The client code does not depend on any subsystem's classes. Any changes * inside the subsystem's code won't affect the client code. You will only need * to update the Facade. */ 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!