Fasada
Typ: Strukturalny
Fasada ma na celu ujednolicenie i uproszczenie wielu, a czasami tylko jednego interfejsu. Prosto mówiąc, fasada agreguje
elementy systemu tworzące większą całość udostępniając uproszczony interfejs pozwalający na skorzystanie z tej całości bez
konieczności dokładnego poznawania każdego pojedynczego elementu.
Kolejną istotną często niedocenianą cechą fasady jest ukrycie nieistotnych z pewnej perspektywy części systemu co nie tylko
upraszcza konkretną funkcjonalność a dodatkowo chroni części systemu, do których dostęp nie jest z poziomu tej funkcjonalności
konieczny.
Przykładowa implementacja
<?php
class SubSystem1
{
public function doSomething()
{
}
}
class SubSystem2
{
public function doSomethingElse()
{
}
}
class SubSystem3
{
public function doEpicStuff()
{
}
public function doLessEpicButsStillImportantStuff()
{
}
}
class Facade
{
private $subSystem1;
private $subSystem2;
private $subSystem3;
public function __construct()
{
$this->subSystem1 = new SubSystem1();
$this->subSystem2 = new SubSystem2();
$this->subSystem3 = new SubSystem3();
}
public function doSomeComplexOperation()
{
$this->subSystem1->doSomething();
$this->subSystem2->doSomethingElseWith();
$this->subSystem3->doEpicStuff();
}
}
Kiedy używać
Czytając Wikipedię można odnieść wrażenie, że wzorzec ten jest wręcz idealny podczas programowania systemu gdzie modelem jest komputer, posiadający system operacyjny, bios czy procesor. Niestety mało kto trafia na tak abstrakcyjne przypadki.
Kolejnym przykładem jest klasa agregująca kilka lub kilkanaście obiektów i udostępniająca metody z tych obiektów poprzez swoje statyczne metody. Przez takie przykłady panuje ogólne przekonanie, że Fasada powinna składać się jedynie z metod statycznych.
Fasady jednak sprawdzają się o wiele bardziej w przypadku kiedy jakaś operacja w systemie jest sekwencją innych operacji, które muszą wykonać się w określonej kolejności, oraz kiedy te operacje wykonywane są w kilku miejscach systemu.
Przykład użycia
Specyfikacja
- należy utworzyć interfejs pozwalający manipulować obrazami
- powinien istnieć interfejs odpowiedzialny za utworzenie nowego obrazu o określonym rozmiarze na podstawie obrazu źródłowego
- powinien istnieć interfejs odpowiedzialny za obrócenie obrazu o podany kąt
- powinien istnieć interfejs odpowiedzialny za zapisanie obrazu z uwzględnieniem określonej jakości
- powinna istnieć możliwość transformacji obrazu źródłowego poprzez pomniejszenie go do rozmiaru 500x500px oraz zapisanie go w 50% oryginalnej jakości
Implementacja
namespace ImageTools;
interface Manipulator
{
public function resize(Image $image, Size $newSize);
public function rotate(Image $image, Angle $angle);
public function changeQuality(Image $image, Percentage $quality);
}
namespace ImageManipulator\Manipulator;
final class GDImageManipulator implements \ImageTools\Manipulator
{
private $gdWrapper;
public function __construct(GDWrapper $gdWrapper)
{
$this->gdWrapper = $gdWrapper;
}
public function resize(Image $image, Size $newSize)
{
if ($newSize->isBiggerThan($this->gdWrapper()->getWidth($image), $this->gdWrapper()->getHeight($image)) {
throw new InvalidImageSizeException("Original image size can't be increased.");
}
return $this->gdWrapper->createNewFrom($image, $newSize->width(), $newSize->height());
}
public function rotate(Image $image, Angle $angle)
{
return this->gdWrapper->rotate($image, $angle->value());
}
public function changeQuality(Image $image, Percentage $quality)
{
return $this->gdWrapper->save($image, $quality->value());
}
}
final class ImageManipulator
{
public function __construct()
{
$this->imageManipulator = new GDImageManipulator(new GDWrapper());
}
public function resize($imagePath, $width, $height, $quality = 75)
{
$image = $this->imageManipulator->resize(new Image($imagePath), new Size($width, $height));
$this->imageManipulator->changeQuality($image, new Percentage($quality));
}
}
class Controller
{
private $imageManipulator;
private $images;
public function __construct(\ImageManipulator\Manipulator\ImageManipulator $imageManipulator, Images $images)
{
$this->imageManipulator = $imageManipulator;
$this->images = $images;
}
public function mediumResizeAction($imageId)
{
$path = $this->images->getImagePathById($imageId);
$this->imageManipulator->resize($path, 500, 500, 50);
return new RedirectResponse('/');
}
}
Omówienie
W powyższym przykładzie fasada nie tylko ułatwia wykonanie operacji zmiany rozmiaru obrazu, ukrywa również
implementację interfejsu ImageManipulatorInterface
. Dzięki takiemu podejściu developer nie musi być świadomy
kolejności wykonywania poszczególnych metod, nie musi również rozumieć jak działa GDWrapper
.