Przejdź do głównej zawartości

[OOP] Na czym polega wzorzec projektowy Fasada?


Wzorzec Fasada polega na zdefiniowaniu uproszczonego interfejsu do złożonego systemu, w sposób eksponujący pewien aspekt tego systemu.

Fasada należy do strukturalnych wzorców projektowych. Więcej o rodzajach wzorców można poczytać tutaj [under construction].

Przykład 1.

Wyobraźmy sobie "inteligentny" dom. Budynek jest nafaszerowany różnego rodzaju czujnikami, na przykład:
  • czujnik statusu okien (otwarte/zamknięte, zasunięte/odsunięte rolety)
  • czujnik temperatury
  • czujnik wilgotności
  • czujnik zapasu paliwa do pieca
  • czujnik w lodówce, mówiący ile mamy jedzenia
  • czujnik w pralni, mówiący ile mamy proszku do prania w zapasie
  • czujnik mówiący ile energii elektrycznej zużywamy w tym momencie
  • i tak dalej
Firma która wybudowała nasz dom udostępnia nam jego API. API to wygląda w ten sposób:

    interface IHouse
    {
        //jedzenie
        int GetTotalKcalStored();
        DateTime GetExpirationDate(FoodProduct p);
        void OrderFoodDelivery(List<FoodProduct> products, string storeName);
        List<FoodProduct> GetAllProductsInHouse();
        List<FoodProduct> GetVegetablesInHouse();
        List<FoodProduct> GetBeveragesInHouse();
        //... i więcej funkcji


        //elektryczność
        float GetTotalUsage();
        float GetAverageUsageLastMonth();
        //... i więcej funkcji

        //temperatura
        float GetBedroomTemperature();
        float GetKitchenTemperature();
        void SetBedroomTemperature(float temp);
        void SetKitchenTemperature(float temp);
        //... i więcej funkcji
    }

Jest to oczywiście uproszczony zapis. Możemy sobie wyobrazić, że API to zawiera dziesiątki lub setki funkcji.

Załóżmy teraz, że chcemy napisać aplikację, która pomoże nam za pomocą smartfona zarządzać temperaturą w domu. Będzie ona w ładny, graficzny sposób pokazywać temperaturę w danych pokojach, informować nas o zapasie paliwa do pieca, pozwoli nam regulować ciepło w pojedynczych pokojach albo i w całym domu. Aplikacja ta zajmować się będzie tylko temperaturą - nie będą nas w niej obchodziły inne aspekty inteligentnego domu.

Możemy oczywiście używać w niej interfejsu IHouse, może to być jednak irytujące. Drażnić może sam fakt że IntelliSense będzie nam podpowiadał dziesiątki funkcji w momencie, gdy interesować nas będzie może piętnaście. Duży interfejs to zawsze większe ryzyko błędów - możemy na przykład przez przypadek użyć funkcji GetBedroomHumidity zamiast GetBedroomTemperature, i odczytać wilgotność zamiast temperatury.

Istnieje jeszcze aspekt enkapsulacji. Wyobraźmy sobie, że chcemy by to zewnętrzna firma napisała aplikację do zarządzania temperaturą w domu. Nie chcemy, by jej pracownicy mieli dostęp do pełnego API - chcemy tylko, by widzieli funkcje związane z temperaturą.

Dla tych powodów warto napisać Fasadę - interfejs eksponujący tylko te aspekty interfejsu IHouse, które mają związek z temperaturą.

    interface IHouseTemperature
    {
        float GetBedroomTemperature();
        float GetKitchenTemperature();
        void SetBedroomTemperature(float temp);
        void SetKitchenTemperature(float temp);
    }

    class HouseTemperature : IHouseTemperature
    {
        private IHouse _house;

        public HouseTemperature(IHouse house)
        {
            _house = house;
        }

        public float GetBedroomTemperature()
        {
            return _house.GetBedroomTemperature();
        }

        public float GetKitchenTemperature()
        {
            return _house.GetKitchenTemperature();
        }

        public void SetBedroomTemperature(float temp)
        {
            _house.SetBedroomTemperature(temp);
        }

        public void SetKitchenTemperature(float temp)
        {
            _house.SetKitchenTemperature(temp);
        }
    }

W przykładzie tym użyliśmy Fasady, by "przyciąć" duży interfejs, eksponując jego wybrane funkcje. Czasem chcemy zrobić coś odwrotnego - utworzyć fasadę agregującą w sobie kilka interfejsów w jedną całość.

Przykład 2.

Wyobraźmy sobie, że piszemy aplikację, której celem będzie udostępnianie danej treści - np. zdjęcia - jednocześnie na wielu serwisach społecznościowych. Przykładowo chcemy móc zrobić zdjęcie, a następnie jednym kliknięciem udostępnić je na Facebooku, Snapchacie, Instagramie i Google+ (ostatnie to oczywiście żart). 

Każdy z serwisów udostępnia swoje własne API:

    interface IFacebook
    {
        void Like(Post post);
        void Unlike(Post post);
        void Share(Post post);
        void AddFriend(User user);
        void UnFriend(User user);
        //i wiele więcej...
    }

    interface IInstagram
    {
        void Like(Post picture);
        void SharePicture(Post picture);
        //i wiele więcej...
    }

    interface ISnapchat
    {
        void SharePicture(Post picture);
        //i wiele więcej...
    }

Skoro nasza aplikacja ma tylko i wyłącznie udostępniać daną treść, to nie interesują nas inne aspekty z API, takie jak dodawanie przyjaciół na Facebooku, klikanie "lubię" na Instagramie itp.

Chcielibyśmy stworzyć jedno, spójne API, które zajmować się będzie tylko udostępnianiem treści. Zobaczmy:

    interface ISharing
    {
        void OnFacebook(Post post);
        void OnInstagram(Post post);
        void OnSnapchat(Post post);
    }

Utworzyliśmy Fasadę, która w uproszczony sposób pozwala nam na udostępnianie treści. Zasłania jednocześnie wszystkie nieinteresujące nas funkcje z dużych, przytłaczających API zewnętrznych.

Ostatecznie widzimy więc, że wzorzec Fasada pozwala nam na:
  • utworzenie spójnego, skoncentrowanego interfejsu, z którego korzystanie jest proste i przejrzyste
  • zablokowanie dostępu do tych aspektów interfejsu, których nie chcemy udostępniać na zewnątrz
  • utworzenia warstwy pośredniej między naszą aplikacją, a zewnętrznym interfejsem, a przez to zmniejszenie zależności między nimi
W poście tym korzystałam z:
  • https://www.baeldung.com/java-facade-pattern
  • https://sourcemaking.com/design_patterns/facade

Komentarze