Przykład koncepcyjny
Poniższy przykład ilustruje strukturę wzorca Fasada ze szczególnym naciskiem na następujące kwestie:
Z jakich składa się klas?
Jakie role pełnią te klasy?
W jaki sposób elementy wzorca są ze sobą powiązane?
Poznawszy strukturę wzorca będzie ci łatwiej zrozumieć następujący przykład, oparty na prawdziwym przypadku użycia PHP.
index.php: Przykład koncepcyjny
<?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: Wynik działania
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
Przykład z prawdziwego życia
Potraktuj Fasadę jako “adapter” upraszczający jakiś skomplikowany podsystem. Fasada ukrywa jego zawiłości w jednej klasie i udostępnia innym elementom programu tylko uproszczony interfejs.
W poniższym przykładzie wzorzec ukrywa złożoność API YouTube i biblioteki FFmpeg przed kodem klienta. Zamiast współdziałać z wieloma klasami, kod klienta korzysta z tylko jednej prostej metody, zdefiniowanej w fasadzie.
index.php: Przykład z prawdziwego życia
<?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: Wynik działania
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!