Hura! Mamy wreszcie przyjemność udostępnić wam polską wersję! Zapraszamy do przesyłania wiadomości z waszymi uwagami i informacjami o zauważonych błędach.

Fasada

Znany też jako: Facade

Cel

Fasada jest strukturalnym wzorcem projektowym, który wyposaża bibliotekę, framework lub inny złożony zestaw klas w uproszczony interfejs.

Wzorzec projektowy Fasada

Problem

Wyobraź sobie, że twój kod musi współdziałać z szerokim zestawem obiektów należących do jakiejś skomplikowanej biblioteki lub frameworku. Zazwyczaj należy zainicjalizować wszystkie te obiekty, śledzić zależności, wywoływać metody w odpowiedniej kolejności i tak dalej.

W rezultacie doszłoby do ścisłego sprzęgnięcia logiki biznesowej twoich klas ze szczegółami implementacji klas innych dostawców, co utrudniłoby utrzymanie i zrozumienie kodu.

Rozwiązanie

Fasada to klasa stanowiąca prosty interfejs dla złożonego podsystemu, zawierającego mnóstwo ruchomych części. Fasada może dawać ograniczoną funkcjonalność, w porównaniu z korzystaniem z elementów podsystemu bezpośrednio, ale za to eksponuje tylko te możliwości, których klient naprawdę potrzebuje.

Stworzenie fasady jest wygodnym sposobem integracji twej aplikacji ze skomplikowaną biblioteką posiadającą wiele funkcji, gdy potrzebujesz tylko wąskiego zakresu jej funkcji.

Na przykład, aplikacja która publikuje krótkie zabawne filmiki z kotami w mediach społecznościowych, może korzystać z profesjonalnej biblioteki konwersji wideo. Z całej biblioteki potrzebuje jednak tylko klasy z jedną metodą — zakoduj(nazwa_pliku, format). Po stworzeniu takiej klasy i podłączeniu jej do biblioteki konwersji wideo otrzymamy naszą pierwszą fasadę.

Analogia do prawdziwego życia

Przykład składania zamówienia przez telefon

Składanie zamówienia przez telefon.

Gdy dzwonisz do sklepu aby złożyć zamówienie, biuro jest twoją fasadą dla wszystkich usług i oddziałów tego sklepu. Pracownik sklepu, czy automat zgłoszeniowy, stanowią prosty interfejs głosowy do systemu zamawiania, płacenia i różnych usług dostawczych.

Struktura

Struktura wzorca projektowego FasadaStruktura wzorca projektowego Fasada
  1. Fasada daje wygodny dostęp do pewnego zakresu funkcjonalności podsystemu. Wie dokąd przekierować żądanie klienta i jak pokierować wszystkimi szczegółami.

  2. Można stworzyć klasę Dodatkowa Fasada, by zapobiec zaśmieceniu pojedynczej fasady niepotrzebnymi funkcjami, które ponownie ograniczyłyby prostotę używania. Dodatkowe fasady mogą być wykorzystane zarówno przez klienta, jak i inne fasady.

  3. Złożony podsystem składa się z wielu różnych obiektów. Aby mogły one wszystkie wykonać coś pożytecznego, trzeba zagłębić się w szczegóły implementacyjne podsystemu — inicjalizację obiektów we właściwej kolejności i przekazanie im danych w odpowiednim formacie.

    Klasy podsystemu nie są świadome istnienia fasady. Działają wewnątrz systemu i współpracują ze sobą bezpośrednio.

  4. Klient korzysta z fasady zamiast wywoływać obiekty podsystemu bezpośrednio.

Pseudokod

W poniższym przykładzie, wzorzec Fasada upraszcza interakcję ze złożonym frameworkiem do konwersji wideo.

Struktura przykładu wzorca projektowego Fasada

Przykład izolowania wielu zależności w pojedynczej klasie fasada.

Zamiast wiązać bezpośrednio kod z wieloma klasami składowymi frameworku, tworzymy klasę fasady która hermetyzuje funkcjonalność i ukrywa ją przed resztą kodu. Ta struktura pozwala też zminimalizować wysiłek związany z aktualizacją frameworku do nowszej wersji lub wręcz wymiany na inny. Jedyna rzecz, jaką trzeba będzie wówczas zmienić, to implementacja metod fasady.

// Oto klasy złożonego frameworku od zewnętrznego dostawcy
// służącego konwersji wideo. Nie mamy wpływu na ten kod, więc
// nie możemy go uprościć.

class VideoFile
// ...

class OggCompressionCodec
// ...

class MPEG4CompressionCodec
// ...

class CodecFactory
// ...

class BitrateReader
// ...

class AudioMixer
// ...


// Tworzymy klasę fasada która ukryje złożoność frameworku za
// prostym interfejsem. Takie podejście jest kompromisem
// pomiędzy funkcjonalnością, a łatwością użycia.
class VideoConverter is
    method convert(filename, format):File is
        file = new VideoFile(filename)
        sourceCodec = new CodecFactory.extract(file)
        if (format == "mp4")
            destinationCodec = new MPEG4CompressionCodec()
        else
            destinationCodec = new OggCompressionCodec()
        buffer = BitrateReader.read(filename, sourceCodec)
        result = BitrateReader.convert(buffer, destinationCodec)
        result = (new AudioMixer()).fix(result)
        return new File(result)

// Klasy aplikacji nie są zależne od masy klas wchodzących w
// skład frameworku. Ponadto, w przypadku konieczności wymiany
// frameworku na inny, trzeba będzie zmodyfikować jedynie klasę
// fasada.
class Application is
    method main() is
        convertor = new VideoConverter()
        mp4 = convertor.convert("funny-cats-video.ogg", "mp4")
        mp4.save()

Zastosowanie

Użyj wzorca Fasada gdy potrzebujesz ograniczonego, ale łatwego w użyciu interfejsu do złożonego podsystemu.

Zazwyczaj podsystemy stają się coraz bardziej złożone z biegiem czasu. Nawet stosowanie wzorców projektowych prowadzi do przyrostu liczby klas. Podsystem może wprawdzie stać się elastyczniejszym i łatwiejszym do ponownego użycia w różnych kontekstach, ale ilość kodu konfigurującego i przygotowawczego wymagana od klienta wzrośnie. Fasada jest sposobem rozwiązania tego problemu poprzez udostępnienie skrótów do najczęściej używanych funkcji podsystemu, zgodnie z wymaganiami klienta.

Stosuj Fasadę gdy chcesz ustrukturyzować podsystem w warstwy.

Twórz fasady by zdefiniować punkty wejścia do każdego poziomu podsystemu. Możesz ograniczyć sprzęgnięcie pomiędzy wieloma podsystemami, zmuszając je do komunikacji ze sobą wyłącznie poprzez fasady.

Dla przykładu, wróćmy do naszego frameworku konwersji wideo. Można go podzielić na dwie warstwy: związaną z obrazem i związaną z dźwiękiem. Dla każdej z warstw możesz utworzyć fasadę i sprawić, by klasy każdej warstwy komunikowały się ze sobą za pośrednictwem tych fasad. Takie podejście przypomina wzorzec Mediator.

Jak zaimplementować

  1. Sprawdź czy możliwe jest utworzenie prostszego interfejsu w porównaniu z tym, jaki dostarcza istniejący podsystem. Jeśli planowany interfejs sprawiłby, że kod kliencki będzie niezależny od wielu klas podsystemu — jesteś na właściwej drodze.

  2. Zadeklaruj i zaimplementuj ten interfejs jako nową klasę fasada. Fasada powinna przekierowywać wywołania pochodzące od kodu klienckiego do stosownych obiektów podsystemu. Ponadto, fasada powinna być odpowiedzialna za inicjalizację podsystemu i zarządzanie jego dalszym cyklem życia, chyba, że kod kliencki już pełni tę rolę.

  3. Aby w pełni skorzystać na tym wzorcu, spraw, aby kod kliencki komunikował się z podsystemem wyłącznie poprzez fasadę. W ten sposób kod kliencki pozostanie chroniony przed zmianami dokonywanymi w kodzie podsystemu. Na przykład, aktualizacja podsystemu będzie wymagała jedynie zmian w kodzie fasady.

  4. Jeśli fasada stanie się zbyt duża, rozważ ekstrakcję części jej obowiązków do nowej, doskonalszej klasy fasady.

Zalety i wady

  • Można odizolować kod od złożoności podsystemu.
  • Fasada może stać się boskim obiektem sprzężonym ze wszystkimi klasami aplikacji.

Powiązania z innymi wzorcami

  • Fasada definiuje nowy interfejs istniejącym obiektom, zaś Adapter zakłada zwiększenie użyteczności zastanego interfejsu. Adapter na ogół opakowuje pojedynczy obiekt, zaś Fasada obejmuje cały podsystem obiektów.

  • Fabryka abstrakcyjna może służyć jako alternatywa do Fasady gdy jedyne co chcesz zrobić, to ukrycie przed kodem klienckim procesu tworzenia obiektów podsystemu.

  • Pyłek przedstawia sposób na stworzenie wielkiej liczby małych obiektów, zaś Fasada na stworzenie pojedynczego obiektu reprezentującego cały podsystem.

  • Fasada i Mediator mają podobne zadania: służą zorganizowaniu współpracy pomiędzy wieloma ściśle sprzęgniętymi klasami.

    • Fasada definiuje uproszczony interfejs podsystemu obiektów, ale nie wprowadza nowej funkcjonalności. Podsystem jest nieświadomy istnienia fasady. Obiekty w obrębie podsystemu mogą komunikować się bezpośrednio.
    • Mediator centralizuje komunikację pomiędzy komponentami podsystemu. Komponenty wiedzą tylko o obiekcie mediator i nie komunikują się ze sobą bezpośrednio.
  • Klasa Fasada może często być przekształcona w Singleton, ponieważ pojedynczy obiekt fasady jest w większości przypadków wystarczający.

  • Fasada przypomina wzorzec Pełnomocnik w tym sensie, że oba buforują złożony podmiot i inicjalizują go samodzielnie. W przeciwieństwie do Fasady, Pełnomocnik ma taki sam interfejs jak obiekt udostępniający usługę który ją reprezentuje, co czyni je wymienialnymi.

Przykłady kodu

Wzorce projektowe: Fasada w języku Java Wzorce projektowe: Fasada w języku C# Wzorce projektowe: Fasada w języku C++ Wzorce projektowe: Fasada w języku PHP Wzorce projektowe: Fasada w języku Python Wzorce projektowe: Fasada w języku Ruby Wzorce projektowe: Fasada w języku Swift Wzorce projektowe: Fasada w języku TypeScript Wzorce projektowe: Fasada w języku Go