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
Prześlij komentarz