Wzorce projektowe – Fasada
Fasada jest jednym z prostszych w zrozumieniu i zastosowaniu wzorców projektowych. Wykorzystanie tego wzorzec jest tak oczywiste i naturalny, że chyba nie ma programisty, który by z niego nie skorzystał. Ideą jest stworzenie uproszczonego interfejsu, które wywołują bardziej złożone części aplikacji. Artykuł ten opisuje wzorzec fasady na bazie prostego przykładu w języku PHP.
Charakterystyka wzorca fasada
Fasada (facade) jest to strukturalny wzorzec projektowy, którego zadaniem jest dostarczenie ujednoliconego, uproszczonego i uporządkowanego interfejsu programistycznego do złożonego systemu. Innymi słowy wzorzec fasada agreguje elementy złożonego systemu, tworząc większą całość oraz udostępniając łatwy w użyciu interfejs. Implementacja zazwyczaj polega na utworzeniu klasy będącej pośrednikiem pomiędzy klientem a systemem.
Poniższy diagram UML pokazuje ogólną strukturę wzorca. Jak widać klasa fasady agreguje poszczególne części systemu w jedną spójną całość i udostępnia interfejs do wykonywania operacji systemu.
Przykład zastosowania wzorca fasada – prosty system bankowy
Poniższy przykład zastosowania wzorca fasady przedstawia bardzo prosty system konta bankowego. Działanie systemu będzie polegało na realizacji dwóch podstawowych funkcjonalności: pobierania i wpłacania pieniędzy na konto. Napiszmy prosty skrypt realizujący te operacje:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require_once '../../../vendor/autoload.php'; use DesignPatterns\Facade\BankAccount\BankAccountFacade; $accessingBank = new BankAccountFacade('123456789', '1234'); $accessingBank->withdrawCash(50.00); $accessingBank->withdrawCash(900.00); $accessingBank->depositCash(200.00); $accessingBank->withdrawCash(400.00); |
Jak widzimy skrypt w pierwszej kolejności tworzy obiekt konta bankowego (tzn. jego fasady), a następnie wywoływane są metody pobierania i wpłacania określonych kwot.
Z punktu widzenia programisty, korzystanie z systemu jest bardzo proste – i o to właśnie chodzi. Natomiast za tą fasadą kryje się o wiele więcej bardziej skomplikowanych operacji, tj.:
- uwierzytelnienie właściciela konta,
- sprawdzenie kodu bezpieczeństwa (PIN),
- sprawdzenie ilości pieniędzy,
- wykonanie operacji powiększania i pomniejszania kwoty.
Wszystkie te funkcjonalności ukryte są za fasadą konta bankowego. Zajmijmy się ich implementacją.
W pierwszej kolejności napiszmy prostą klasę powitalną dla konta bankowego:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
namespace DesignPatterns\Facade\BankAccount; /** * Basic class which will welcome bank client */ class WelcomeToBank { public function __construct() { echo 'Witamy w banku ABC S.A.'; echo PHP_EOL; } } |
Następnie napiszmy klasę sprawdzająca numer konta i jego aktywność oraz klasę sprawdzającą kod zabezpieczający:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
namespace DesignPatterns\Facade\BankAccount; class AccountNumberCheck { private $accountNumber = '123456789'; public function getAccountNumber() { return $this->accountNumber; } public function accountActive($accountNumberToCheck) { if($accountNumberToCheck == $this->getAccountNumber()) { return true; } return false; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
namespace DesignPatterns\Facade\BankAccount; class SecurityCodeCheck { private $securityCode = '1234'; public function getSecurityCode() { return $this->securityCode; } public function isCodeCorrect($securityCodeToCheck) { if($securityCodeToCheck == $this->getSecurityCode()) { return true; } return false; } } |
Numer konta i kod są w powyższych klasach predefiniowane, natomiast w prawdziwym systemie te dane pochodziłby prawdopodobnie z bazy danych. Nie jest to jednak potrzebne do przedstawienia idei wzorca.
Kolejnym elementem aplikacji jest klasa realizująca działania na kwocie zdeponowanej w banku:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
namespace DesignPatterns\Facade\BankAccount; class FundsCheck { private $cashInAccount = 1000.00; public function getCashInAccount() { return $this->cashInAccount; } public function decreaseCashInAccount($cashWithdrawn) { $this->cashInAccount -= $cashWithdrawn; } public function increaseCashInAccount($cashDeposit) { $this->cashInAccount += $cashDeposit; } public function haveEnoughMoney($cashToWithdrawal) { if($cashToWithdrawal > $this->getCashInAccount()) { echo 'Błąd. Brak wystarczającej ilości środków na koncie'.PHP_EOL; echo 'Aktualny stan konta: '.$this->getCashInAccount().PHP_EOL; return false; } return true; } public function makeWithdrawn($cashToWithdrawal) { $this->decreaseCashInAccount($cashToWithdrawal); echo 'Wypłata zakończona: Aktualny stan konta wynosi '.$this->getCashInAccount().PHP_EOL; } public function makeDeposit($cashToDeposit) { $this->increaseCashInAccount($cashToDeposit); echo 'Wpłata zakończona: Aktualny stan konta wynosi '.$this->getCashInAccount().PHP_EOL; } } |
W powyższej klasie początkowa kwota na koncie jest przykładowa i na tej kwocie wykonywane są różne funkcje bankowe.
Mając napisane już wszystkie elementy aplikacji zabierzmy się za napisanie fasady, tzn. klasy agregującej wszystkie funkcjonalności oraz dostarczającej klientowi uproszczony interfejs do obsługi konta bankowego.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
namespace DesignPatterns\Facade\BankAccount; use DesignPatterns\Facade\BankAccount\WelcomeToBank; use DesignPatterns\Facade\BankAccount\AccountNumberCheck; use DesignPatterns\Facade\BankAccount\SecurityCodeCheck; use DesignPatterns\Facade\BankAccount\FundsCheck; class BankAccountFacade { private $accountNumber; private $securityCode; private $accountChecker; private $codeChecker; private $fundChecker; private $bankWelcome; public function __construct($accountNumber, $securityCode) { $this->accountNumber = $accountNumber; $this->securityCode = $securityCode; $this->bankWelcome = new WelcomeToBank(); $this->accountChecker = new AccountNumberCheck(); $this->codeChecker = new SecurityCodeCheck(); $this->fundChecker = new FundsCheck(); } public function getAccountNumber() { return $this->accountNumber; } public function getSecurityCode() { return $this->securityCode; } public function withdrawCash($cashToGet) { if($this->accountChecker->accountActive($this->getAccountNumber()) && $this->codeChecker->isCodeCorrect($this->getSecurityCode()) && $this->fundChecker->haveEnoughMoney($cashToGet)) { $this->fundChecker->makeWithdrawn($cashToGet); echo 'Transakcja zakończona'.PHP_EOL; } else { echo 'Transakcja nie powiodła się'.PHP_EOL; } } public function depositCash($cashToDeposit) { if($this->accountChecker->accountActive($this->getAccountNumber()) && $this->codeChecker->isCodeCorrect($this->getSecurityCode())) { $this->fundChecker->makeDeposit($cashToDeposit); echo 'Transakcja zakończona'.PHP_EOL; } else { echo 'Transakcja nie powiodła się'.PHP_EOL; } } } |
Testowe wywołanie działania aplikacji zaprezentowane zostało na początku artykułu, a wynik prezentuje się następująco:
1 2 3 4 5 6 7 8 9 10 |
Witamy w banku ABC S.A. Wypłata zakończona: Aktualny stan konta wynosi 950 Transakcja zakończona Wypłata zakończona: Aktualny stan konta wynosi 50 Transakcja zakończona Wpłata zakończona: Aktualny stan konta wynosi 250 Transakcja zakończona Błąd. Brak wystarczającej ilości środków na koncie Aktualny stan konta: 250 Transakcja nie powiodła się |
Użytkownik mająca na koncie wartość 1000, najpierw wypłaca 50, następnie wypłaca 900, a później wpłaca 200 i jak do tej pory wszystkie te operacje przechodzą bezproblemowo. Następnie próbuje wypłacić więcej środków niż zostało mu na rachunku i tutaj system zgłasza błąd. Jak widzimy wszystkie elementy aplikacji zadziałały poprawnie.
Wszystkie części systemu są oddzielone od siebie i realizują pojedyncze czynności. Można je łatwo rozszerzać lub dopisywać nowe funkcjonalności. Klasa fasady spaja cały system w jeden spójny, łatwy w użyciu interfejs.
Powyższy przykład można pobrać tutaj: bank-account
Przykład na GitHub: https://github.com/molitorys/design-patterns/tree/master/src/Facade/BankAccount