Jak zaimplementować wzorzec facade w C#
Po co nam Facade design pattern?
Przyjrzyj się uważnie poniższej ilustracji. Klasa po prawej stronie potrzebuje czterech metod, które należą do klasy po lewej stronie. Jednak aby określić, do których z czterech metod musimy uzyskać dostęp, prawdopodobnie będziemy musieli pogrzebać w organizacji wewnętrznej najbardziej wysuniętej na lewo ClassA
. Albo… istnieje lepsze rozwiązanie!
Mogę po prostu umieścić fasadę pomiędzy moimi klasami. Teraz ta fasada ma wewnętrzne odniesienie do ClassA
i organizuje wywołania w imieniu klasy Program. Używamy klasy fasady, która zawiera ClassA
i udostępnia tylko te metody, które są potrzebne naszemu programowi. Możemy używać bardziej znaczących nazw metod, a ponadto nasz program będzie rozmawiał tylko z fasadą, nie wiedząc nic o ClassA
.
Skalowalny i złożony projekt zawiera oczywiście setki klas. Ten wzorzec pomaga naszej klasie wywołującej, dzięki skupieniu się na tym, co jest w tym momencie ważne, zamiast przeglądania wszystkiego (abstrakcja w programowaniu obiektowym).
Załóżmy, że chcemy zaimplementować klasę dla smartfona, która ma 3 specyfikacje: procesor, pamięć (RAM, ROM) i wyświetlacz. Nasza implementacja może przedstawiać się następująco:
Listing 1: klasa Processor
—
namespace FacadeDesignPattern
{
public class Processor
{
public void DisplayProcessor()
{
System.Console.WriteLine("Snapdragon 635");
}
}
}
Listing 2: klasa Memory
—
namespace FacadeDesignPattern
{
public class Memory
{
public void AssembleMemory()
{
System.Console.WriteLine("8GB RAM + 128GB ROM");
}
}
}
Listing 3: klasa Display
:
namespace FacadeDesignPattern
{
public class Display
{
public void AssembleDisplay()
{
System.Console.WriteLine("Retina Display");
}
}
}
Listing 4: klasa wywołująca, klasa SmartPhone
:
namespace FacadeDesignPattern
{
class SmartPhone
{
static void Main(string[] args)
{
Console.WriteLine("One Plus 8");
//calling processor
Processor processor = new Processor();
processor.AssembleProcessor();
//calling memory
Memory memory = new Memory();
memory.AssembleMemory();
//calling display
Display display = new Display();
display.AssembleDisplay();
}
}
}
Jest to prosty problem, ale ponieważ smartfony muszą podążać za najnowszymi trendami i innowacjami, trzeba będzie dodać więcej klas dla każdej funkcji.
Aby ponownie zaimplementować smartPhone
, trzeba przebrnąć przez wszystkie te klasy, a tutaj mamy tylko 3: Display
, Memory
, Processor.
W prawdziwym projekcie, jak już wspomniałem, mielibyśmy do czynienia z setkami klas, co utrudnia znalezienie tych potrzebnych do implementacji nowego smartPhone
.
Jesteśmy w stanie dodać warstwę pośrednią, która będzie odgrywała rolę fasady dla naszego smartfona i która zajmie się wszystkimi funkcjami niezbędnymi do stworzenia smartfona. Jeśli chcemy, aby nasz wzorzec był bardziej rozwinięty, możemy dodać interfejs, za pomocą którego nasza klasa smartPhone
będzie wchodzić w interakcje.
Dlaczego interfejs? Dobre pytanie.
Ta abstrakcja pozwala programowi wywołującemu (w tym przypadku smartPhone
) pracować wyłącznie z fasadą i tym samym uprościć nasz kod. Wprowadzenie w tym miejscu interfejsu naprawdę porządkuje sprawy dla programu żądającego i umożliwia łatwe dodawanie nowych klas fasady do bazowych klas serwisowych (więcej funkcji, więcej klas), które będą zmieniały się w przyszłości (bardziej zaawansowane klasy smartfonów).
Jeśli jutro na przykład wypuściłbym na rynek nowy smartfon z systemem Android 10 o nowej, wysokiej klasie specyfikacji, to mógłbym to osiągnąć poprzez interakcję z interfejsem.
Skoro już o tym mowa, zacznijmy pracę z Facade design pattern.
A gdyby tak umieścić procesor, pamięć i wyświetlacz za fasadą, aby smartfon nie musiał się martwić o powyższą złożoność?
Krok 1
Listing 5: Dodajmy interfejs ISmartPhoneFacade
:
namespace FacadeDesignPattern
{
interface ISmartPhoneFacade
{
void ShowSmartPhoneSpecifications();
}
}
Krok 2
Listing 6: Teraz dodajmy klasę SmartPhoneFacade:
namespace FacadeDesignPattern
{
public class SmartPhoneFacade : ISmartPhoneFacade
{
private Display display;
private Memory memory;
private Processor processor;
public SmartPhoneFacade() : this(new Display(), new Memory(), new Processor())
{
}
public SmartPhoneFacade(Display display, Memory memory, Processor processor)
{
this.memory = memory;
this.display = display;
this.processor = processor;
}
public void ShowSmartPhoneSpecifications()
{
display.AssembleDisplay();
memory.AssembleMemory();
processor.DisplayProcessor();
}
}
}
Listing 7: Teraz spójrzmy nieco prościej na naszą klasę smartPhone
:
using System;
namespace FacadeDesignPattern
{
class SmartPhone
{
static void Main(string[] args)
{
ISmartPhoneFacade smartPhone = new SmartPhoneFacade();
smartPhone.ShowSmartPhoneSpecifications();
}
}
}
Jeśli jutro pojawi się nowe urządzenie smartPhone
, wystarczy, że utworzę nową fasadę, specyficzną dla funkcji smartPhone i dziedziczącą po interfejsie ISmartPhoneFacade
. To jest właśnie siła abstrakcji w programowaniu obiektowym.
Uwaga: dodanie dodatkowej klasy może wydawać się kłopotliwe, ale w szerszej perspektywie jest to pomocne i właśnie w tym miejscu można zaprojektować klasy, które są luźno powiązane.
Mam szczerą nadzieję, że podobał Ci się ten artykuł i że zainspirował Cię do zastosowania zdobytej wiedzy w twoich własnych aplikacjach.
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.