Jak zaimplementować wzorzec facade w C#
Sprawdź, jak zaimplementować popularny wzorzec projektowy krok po kroku.
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.