Fasada jest wzorcem zaliczającym się do wzorców strukturalnych. Jest to jeden z popularniejszych, a jednocześnie najprostszych wzorców projektowych.
Wyobraź sobie sytuację, gdy masz złożony
system. W pewnym momencie musisz
zapewnić funkcjonalność, która możesz
osiągnąć poprzez złożenie kilku usług oferowanych
przez klasy wchodzące w skład systemu. Może to
wyglądać na przykład tak – naliczanie rabatu do
zakupów klienta:
$objKlient = new Klient($intIdKlienta);
$objKoszyk = new Koszyk($objKlient);
$objRabat = new Rabat();
$fltRabat = $objRabat->liczRabat($objKoszyk->getSumaKoszyka()+ $objKlient->getRabatIndywidualny());
Klient klient = new Klient(idKlienta);
Koszyk koszyk = new Koszyk(klient);
Rabat rabat = new Rabat();
Float Rabat = rabat.liczRabat(koszyk.getSumaKoszyka() + klient.getRabatIndywidualny());
Nie ma się czym przejmować, jeśli taka
konstrukcje mamy w jednym miejscu. Jeśli jednak
podobna konstrukcja będzie potrzebna w wielu
miejscach, warto zadbać o to, aby nie kopiować jej
wszędzie za pomocą kombinacji CTRL+C CRTL+V.
W przeciwnym razie w przypadku konieczności
zmodyfikowania powyższej formuły, staniemy
przed koniecznością odszukania poprawianego
fragmentu wszędzie tam gdzie go skopiowaliśmy
i poprawieniem go w tych wszystkich miejscach
z osobna. Tu z pomocą przychodzi fasada.
Przykładowa aplikacja
Tym razem ze względu na sposób działania
wzorca i opis problemu zamieszczony powyżej,
nasza aplikacja będzie od razu opierała się na prezentowanym
wzorcu. Będzie to klasa zarządzająca
przyznawaniem rabatów w sklepie internetowym.
PHP:
class FasadaRabatów {
private $objKlient;
private $objRabat;
private $objKoszyk;
private $objZamowienia;
public function_construct($intIdKlienta) {
$this->objKlient = new Klient($intIdKlienta);
$this->objRabat = new Rabat($this->objKlient);
$this->objKoszyk = new Koszyk();
$this->objZamowienia = new Zamowienia($this->objKlient);
}
public function rabatIndywidualny() {
return $this->objKlient->getRabatIndywidulany();
}
public function
rabatOdZakupowWKoszyku() {
return $this->objRabat->liczRabat($this->objKoszyk->getWartosc());
}
public function
getRabatOdWielkosciZamowien() {
return $this->objRabat->liczRabat($this->objZamowienia->getSume());
}
}
public class FasadaRabatow {
private Klient klient;
private Rabat rabaty;
private Koszyk koszyk;
private Zamowienia zamowienia;
public function FasadaRabatow(IntegeridKlienta) {
klient = new Klient(idKlienta);
rabaty = new Rabat(klient);
koszyk = new Koszyk();
zamowienia = new Zamowienia(klient);
}
public Float rabatIndywidualny() {
return klient->getRabatIndywidulany();
}
public Float rabatOdZakupowWKoszyku() {
return rabat->liczRabat(koszyk->getWartosc());
}
public Float
getRabatOdWielkosciZamowien() {
return rabat->liczRabat(zamowienia->getSume());
}
}
Poniżej prezentujemy jeszcze szkielety klas
Klient, Rabat, Koszyk i Zamowienia. Jak zawsze jest
to jedynie szkielet, nie zawierający wartościowego
kodu, mający na celu jedynie zaprezentowanie
działania wzorca:
class Klient {
private $strImie;
private $strNazwisko;
private $fltRabat;
// ... inne dane o kliencie ...
public function __ construct($intIdKlienta) {
//pobranie informacji o kliencie
}
public function getImie() {return $this->strImie;
}
public function getNazwisko() {
return $this->strNazwisko;
}
public function getRabatIndywidualny()
{
return $this->fltRabat;
}
}
class Rabat {
private $objKlient;
public function __construct(Klient $objKlient) {
$this->objKlient = $objKlient;
}
public function liczRabat() {
//liczenie rabatu
return $fltRabat;
}
}
class Koszyk {
public function getProdukty() {
// wyciągniecie informacji o produktach w koszyku
return $arrKoszyk;
}
public function dodajProdukt($intId) {
// dodanie produktu do koszyka
}
public function usunProdukt($intId) {
// usunięcie produktu z koszyka
}
public function getWartosc() {
// obliczenie wartości produktów w koszyku
return $fltWartosc;
}
}
class Zamowienia {
private $objKlient;
public function __construct(Klient $objKlient) {
$this->objKlient = $objKlient;
}
public function zamow(Koszyk $objKoszyk) {
// zamówienie produktów z koszyka
}
public function getSume() {
//oblicz sumę wszystkich zrealizowanych zamówień
return $fltSuma;
}
}
public class Klient {
private String imie;
private String nazwisko;
private Float rabat;
// ... inne dane o kliencie ...
public function Klient(Integer idKlienta) {
//pobranie informacji o kliencie
}
public String getImie() {
return imie;
}
public String getNazwisko() {
return nazwisko;
}
public Float getRabatIndywidualny() {
return rabat;
}
}
public class Rabat {
private Klient klient;
public function __construct(Klient klient) {
this.klient = klient;
}
public Float liczRabat() {
//liczenie rabatu
return rabat;
}
}
public class Koszyk {
public ArrayList
getProdukty() {
// wyciągniecie informacji o produktach w koszyku
return koszyk;
}
public void dodajProdukt(Integer id) {
// dodanie produktu do koszyka
}
public void usunProdukt(Integer id) {
// usunięcie produktu z koszyka
}
public Float getWartosc() {
// obliczenie wartości produktów w koszyku
return wartosc;
}
}
public class Zamowienia {
private Klient klient;
public function_construct(Klient klient) {
this.klient = klient;
}
public void zamow(Koszyk koszyk) {
// zamówienie produktów z koszyka
}
public Float getSume() {
//oblicz sumę wszystkich zrealizowanych zamówień
return suma;
}
}
Jak to działa? Program wszędzie tam, gdzie
potrzeba jest na przykład wartość rabatu od
sumy produktów w koszyku klienta, nie składa
za każdym razem kilku klas, a korzysta z jednej,
FasadaRabatów i z jej metody rabatOdZakupowWKoszyku(). Podsumowując, fasada posłużyła do
zbudowania często używanych metod z klocków
jakimi jest zbiór kilku klas systemu.
Poznałeś już kilka wzorców projektowych. Ich
liczba jest już wystarczająca aby analizować swoje
programy i zastanowić się gdzie i co mogłeś
zrobić lepiej. Warto też zastanowić się jak można
skutecznie połączyć klika wzorców projektowych
w jeden.
Kiedy używać i co nam to daje
Nad zastosowaniem fasady warto zastanowić się wówczas, gdy chcesz obudować złożony system
prostym interfejsem lub w systemie występuje wiele zależności pomiędzy klasami abstrakcyjnymi
a klasami je implementującymi.
Dzięki fasadzie można uniezależnić podsystem i zapewnić jego przenośność. A dokładniej, oddzielić
kod kliencki od komponentów, dostarczając mu jeden (lub klika, ale zawsze mniej niż istnieje komponentów)
interfejs obsługi podsystemu. Sprzyja to łatwiejszemu użyciu. Komponenty zostają o wiele
słabiej powiązane z kodem klienckim, dzięki czemu można je łatwiej podmienić w kodzie fasady, co nie
powinno mieć wpływu na działanie kodu klienckiego.
Pamiętaj jednak że fasada nie blokuje dostępu do komponentów, które obudowuje. W kodzie kliencki
możesz użyć fasady lub bezpośrednio komponentów – w zależności od potrzeby.
Fasada w pigułce
- Wyodrębnij rodziny działań które w systemie
często są wykonywane, -
Działania z tej samej rodziny skomponuj
w metody nowej klasy, - W metodach klasy fasady użyj do wykonania
zaplanowanych działań innych obiektów
systemu.