Wzorzec projektowy Fasada to kolejny ze wzorców projektowych opisanych w książce Design Patterns: Elements of Reusable Object-Oriented Software przez tzw. bandę czworga (Gang of Four) – Gamma Erich, Helm Richard, Johnson Ralph oraz Vlissides John. Jest to jeden ze wzorców należących do kategorii wzorców strukturalnych. Jeśli chcesz poznać inne wzorce projektowe lub dowiedzieć się czym są wzorce, to koniecznie sprawdź mój wpis o wzorcach projektowych.
Charakterystyka wzorca Fasada
Tak jak w wielu innych wzorcach, tak i w przypadku Fasady łatwo można odnaleźć analogie w świecie rzeczywistym. Fasadą, z którą z pewnością się spotkałeś/aś jest bankomat. Bankomat jest interfejsem pośredniczącym między bankiem, dzięki któremu można:
- wypłacić środki z konta za pomocą karty płatniczej,
- wypłacić środki z konta za pomocą kodu BLIK,
- sprawdzić stan konta.
Dzięki bankomatowi — Fasadzie, cała komunikacja między użytkownikiem a bankiem jest ukryta. Użytkownik nie musi wiedzieć, jak działa BLIK, nie musi przepisywać danych z karty, ani nawet wiedzieć, do jakiego oddziału banku jest przypisany. Interakcja z użytkownikiem ograniczona jest do absolutnego minimum.
Takie same założenia zostały przyjęte dla wzorca projektowego Fasada. Ma ona za zadanie ukryć wszelkie szczegóły implementacyjne i detale, które nie są do niczego potrzebne użytkownikowi. Jednocześnie Fasada ma zadanie wystawić przyjazny interfejs dla użytkownika docelowego.
Praktyczny przykład
Mając już za sobą teoretyczny wstęp, czas przejść do praktyki. W tej części artykułu przedstawię Ci praktyczne zastosowanie wzorca Fasada. Postawionym zadaniem będzie zaprojektowanie aplikacji, która ma jedną publiczną metodę, która oczekuje podania treści w formacie kodu HTML oraz oczekiwanego formatu pliku wynikowego. Rezultatem zwróconym z omawianej metody ma być plik w podanym formacie. Diagram klas UML dla omawianego rozwiązana wygląda następująco.
Przedstawione rozwiązanie pozwala na wygenerowanie dokumentu w formatach: DOCX, PDF i Markdown. Interfejs ogranicza interakcję z użytkownikiem do wymagań określonych wcześniej — jedna metoda z dwoma parametrami. Dzięki temu użytkownik nie musi wiedzieć absolutnie nic o tym, jak aplikowane są przykładowo czcionki czy zdjęcia w dokumentach w wymienionych formatach. Powyższy diagram klas UML zaimplementowany w TypeScript wygląda następująco.
interface IPrinter {
getFormat(): string;
print( content: string ): Promise<Buffer>;
}
class MarkdownPrinter implements IPrinter {
private readonly _format: string = 'md';
public getFormat(): string {
return this._format;
}
public print( content: string ): Promise<Buffer> {
// ... magic
}
}
class DOCXPrinter implements IPrinter {
private readonly _format: string = 'docx';
public getFormat(): string {
return this._format;
}
public print( content: string ): Promise<Buffer> {
// ... magic
}
}
class PDFPrinter implements IPrinter {
private readonly _format: string = 'pdf';
public getFormat(): string {
return this._format;
}
public print( content: string ): Promise<Buffer> {
// ... magic
}
}
class DocumentFacade {
private readonly _printers: IPrinter[];
public constructor() {
this._printers = [
new DOCXPrinter(),
new MarkdownPrinter(),
new PDFPrinter()
];
}
public printDocument( format: string, content: string ): Promise<Buffer> {
const printer: IPrinter | undefined = this._printers.find( printer => printer.getFormat() === format );
if ( !printer ) {
throw new Error( 'Document format not supported.' );
}
return printer.print( content );
}
}
Powyższy kod w formie interaktywnej znajdziesz pod tym linkiem. W dalszych etapach rozwoju tego programu nic nie stoi na przeszkodzie, by przykładowo podpisywać czy szyfrować generowany dokument poprzed dodatkową metodę w klasie DocumentFacade
lub dodatkowy parametr w metodzie printDocument
.
Analogie do innych wzorców projektowych
Różnica między Fasadą a Proxy jest to, że celem Proxy jest pośredniczenie między użytkownikiem a kodem docelowym. Przykładowo, chcąc wykonać zapytanie HTTP o obecną temperaturę powietrza w podanej lokalizacji, w przypadku Proxy, oczekiwałbym interfejsu pozwalającego zdefiniować URL, metodę, nagłówki itp. Samo Proxy dodawałoby tylko nagłówki uwierzytelniające. Natomiast w przypadku Fasady zaimplementowałbym metodę do pobierania temperatury, która wymaga jedynie podania miasta, dla którego wykonane ma być zapytanie. Użytkownik nawet nie wiedziałby o tym, że wewnątrz klasy wykonane jest zapytanie HTTP.
Różnica między Fabryką a Fasadą wynika głównie z celu powstania klasy — Fabryka służy do ukrycia szczegółów konstrukcji wytwarzanych obiektów, z kolei Fasada służy ukryciu szczegółów implementacyjnych zaimplementowanego mechanizmu i wystawieniu przyjaznego interfejsu.
Adapter różni się od Fasady tym, że założeniem Adaptera jest zapewnienie kompatybilności między dwoma niekompatybilnymi interfejsami.
Zdarza się też tak, że Fasada jest jednocześnie Singletonem.
Podsumowanie
Mam nadzieję, że idea oraz cel stosowania wzorca projektowego Fasada jest już dla Ciebie zrozumiała! Serdecznie zachęcam do pozostawienia komentarza, szczególnie jeśli masz jakieś uwagi lub wątpliwości. Będzie mi również bardzo miło, jeśli udostępnisz ten wpis, aby trafił do większej liczby czytelników.
Książkę Design Patterns: Elements of Reusable Object-Oriented Software możesz kupić klikając w TEN link. Kupując z tego linku, wspierasz rozwój bloga.
Źródła i materiały dodatkowe
- Design Patterns: Elements of Reusable Object-Oriented Software – Gamma Erich, Helm Richard, Johnson Ralph, Vlissides John
- Facade Design Pattern in Java
- Design Patterns — A quick guide to Facade pattern
- Facade Design Pattern in Java
- Refactoring Guru – Fasada
- Sourcemaking – Facade
- Difference between the Facade, Proxy, Adapter and Decorator design patterns?
- What are the differences between facade pattern and abstract factory pattern?
Dość ciekawie przedstawiony temat, dzięki!