Abstrakcyjny wzór fabryki - Abstract factory pattern
Fabryka abstrakcyjna zapewnia sposób otoczyć grupę poszczególnych fabryk , które mają wspólny motyw bez określania ich klas betonu. W normalnym użytkowaniu oprogramowanie klienckie tworzy konkretną implementację abstrakcyjnej fabryki, a następnie używa ogólnego interfejsu fabryki do tworzenia konkretnych obiektów, które są częścią motywu. Klient nie wie (lub opieki), który konkretnych przedmiotów robi się z każdym z tych wewnętrznych fabryk, ponieważ używa tylko ogólne interfejsy swoich produktów. Wzorzec ten oddziela szczegóły implementacji zbioru obiektów od ich ogólnego użycia i opiera się na kompozycji obiektów, ponieważ tworzenie obiektów jest implementowane w metodach udostępnionych w interfejsie fabryki.
Przykładem może być abstrakcyjna klasa fabryki, DocumentCreator
która udostępnia interfejsy do tworzenia wielu produktów (np. createLetter()
i createResume()
). System miałby dowolną liczbę pochodnych konkretnych wersji DocumentCreator
klasy, takich jak FancyDocumentCreator
lub ModernDocumentCreator
, z których każda miałaby inną implementację createLetter()
i createResume()
która tworzyłaby odpowiedni obiekt, taki jak FancyLetter
lub ModernResume
. Każdy z tych produktów pochodzi z prostej abstrakcyjnej klasy, takiej jak Letter
lub Resume
której klient jest świadomy. Kod klient dostanie odpowiednią instancję z poniższych DocumentCreator
i wywołać jego metody fabrycznych . Każdy z wynikowych obiektów byłby stworzony z tej samej DocumentCreator
implementacji i miałby wspólny motyw (wszystkie byłyby fantazyjnymi lub nowoczesnymi obiektami). Klient musiałby tylko wiedzieć, jak obsłużyć abstrakt Letter
lub Resume
klasę, a nie konkretną wersję, którą otrzymał z fabryki betonu.
Fabryka to lokalizacja konkretnej klasy w kodzie, w której konstruowane są obiekty . Celem zastosowania wzorca jest odizolowanie tworzenia obiektów od ich użycia i tworzenie rodzin powiązanych obiektów bez konieczności polegania na ich konkretnych klasach. Pozwala to na wprowadzenie nowych typów pochodnych bez zmian w kodzie, który używa klasy bazowej .
Użycie tego wzorca umożliwia wymianę konkretnych implementacji bez zmiany kodu, który ich używa, nawet w czasie wykonywania . Jednak zastosowanie tego wzorca, podobnie jak w przypadku podobnych wzorców projektowych , może skutkować niepotrzebną złożonością i dodatkową pracą przy początkowym pisaniu kodu. Ponadto wyższe poziomy separacji i abstrakcji mogą spowodować, że systemy będą trudniejsze do debugowania i utrzymania.
Przegląd
Wzorzec projektowy Fabryka abstrakcyjna jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych GoF, które opisują sposób rozwiązywania powtarzających się problemów projektowych w celu projektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów, które są łatwiejsze do wdrożenia, zmiany, testowania, i ponownie wykorzystać.
Wzorzec projektowy Fabryka abstrakcyjna rozwiązuje takie problemy jak:
- W jaki sposób aplikacja może być niezależna od sposobu tworzenia jej obiektów?
- Jak klasa może być niezależna od tego, jak tworzone są wymagane przez nią obiekty?
- Jak tworzyć rodziny powiązanych lub zależnych obiektów?
Tworzenie obiektów bezpośrednio w klasie, która wymaga tych obiektów, jest nieelastyczne, ponieważ zatwierdza klasę do poszczególnych obiektów i uniemożliwia późniejszą zmianę instancji niezależnie od (bez konieczności zmiany) klasy. Uniemożliwia ponowne użycie klasy, jeśli wymagane są inne obiekty, i utrudnia testowanie klasy, ponieważ obiektów rzeczywistych nie można zastąpić obiektami pozorowanymi.
Wzorzec projektowy Fabryka abstrakcyjna opisuje, jak rozwiązać takie problemy:
- Hermetyzuj tworzenie obiektu w oddzielnym (fabrycznym) obiekcie. Oznacza to, że zdefiniuj interfejs (AbstractFactory) do tworzenia obiektów i zaimplementuj ten interfejs.
- Klasa deleguje tworzenie obiektów do obiektu fabryki zamiast bezpośrednio tworzyć obiekty.
To sprawia, że klasa jest niezależna od tego, jak tworzone są jej obiekty (które konkretne klasy są tworzone). Klasę można skonfigurować za pomocą obiektu fabryki, którego używa do tworzenia obiektów, a co więcej, obiekt fabryki można wymieniać w czasie wykonywania.
Definicja
Istotą wzorca fabryki abstrakcyjnej jest „zapewnienie interfejsu do tworzenia rodzin powiązanych lub zależnych obiektów bez określania ich konkretnych klas”.
Stosowanie
Fabryka określa rzeczywisty konkretny typ obiektu ma zostać utworzony, i to jest tutaj, że obiekt jest rzeczywiście stworzył (w Javie, na przykład, przez nowego operatora ). Jednak fabryka zwraca tylko abstrakcyjny wskaźnik do utworzonego konkretnego obiektu .
To izoluje kod klienta od tworzenia obiektu , ponieważ klienci proszą obiekt fabryki o utworzenie obiektu żądanego typu abstrakcyjnego i zwrócenie abstrakcyjnego wskaźnika do obiektu.
Ponieważ fabryka zwraca tylko abstrakcyjny wskaźnik, kod klienta (który zażądał obiektu z fabryki) nie zna — i nie jest obciążony — faktycznym konkretnym typem właśnie utworzonego obiektu. Jednak typ konkretnego obiektu (a więc konkretnej fabryki) jest znany fabryce abstrakcyjnej; na przykład fabryka może odczytać go z pliku konfiguracyjnego. Klient nie musi określać typu, ponieważ został już określony w pliku konfiguracyjnym. W szczególności oznacza to:
- Kod klienta nie ma żadnej wiedzy o konkretnym typie , nie musi zawierać żadnych plików nagłówkowych ani związanych z nim deklaracji klas . Kod klienta zajmuje się tylko typem abstrakcyjnym. Obiekty konkretnego typu są rzeczywiście tworzone przez fabrykę, ale kod klienta uzyskuje dostęp do takich obiektów tylko poprzez ich abstrakcyjny interfejs .
- Dodawanie nowych typów konkretnych odbywa się poprzez modyfikację kodu klienta tak, aby korzystał z innej fabryki, modyfikacja, która zwykle obejmuje jeden wiersz w jednym pliku. Następnie inna fabryka tworzy obiekty innego konkretnego typu, ale nadal zwraca wskaźnik tego samego typu abstrakcyjnego co poprzednio — w ten sposób izolując kod klienta przed zmianami. Jest to znacznie łatwiejsze niż modyfikowanie kodu klienta w celu utworzenia instancji nowego typu, co wymagałoby zmiany każdej lokalizacji w kodzie, w której tworzony jest nowy obiekt (a także upewnienia się, że wszystkie takie lokalizacje kodu również mają wiedzę o nowym konkretnym typie, dołączając na przykład plik nagłówka konkretnej klasy). Jeśli wszystkie obiekty fabryki są przechowywane globalnie w obiekcie singleton , a cały kod klienta przechodzi przez ten obiekt, aby uzyskać dostęp do właściwej fabryki w celu utworzenia obiektu, wówczas zmiana fabryk jest tak prosta, jak zmiana obiektu singleton.
Struktura
Schemat UML
Na powyższym diagramie klas UML klasa , Client
która wymaga ProductA
i ProductB
obiekty nie tworzy bezpośrednio instancji klasy ProductA1
i ProductB1
. Zamiast tego Client
odnosi się do AbstractFactory
interfejsu do tworzenia obiektów, który Client
uniezależnia sposób tworzenia obiektów (które konkretnie klasy są tworzone). Factory1
Klasa implementuje AbstractFactory
interfejs poprzez utworzenie wystąpienia ProductA1
i ProductB1
zajęcia.
W UML Schemat sekwencji pokazuje interakcje Run-Time: Client
calls obiektu createProductA()
na Factory1
obiekt, który tworzy i zwraca ProductA1
obiekt. Następnie Client
wywołuje createProductB()
on Factory1
, który tworzy i zwraca ProductB1
obiekt.
Wykres LePUS3
Przykład w Pythonie
from abc import ABC, abstractmethod
from sys import platform
class Button(ABC):
@abstractmethod
def paint(self):
pass
class LinuxButton(Button):
def paint(self):
return "Render a button in a Linux style"
class WindowsButton(Button):
def paint(self):
return "Render a button in a Windows style"
class MacOSButton(Button):
def paint(self):
return "Render a button in a MacOS style"
class GUIFactory(ABC):
@abstractmethod
def create_button(self):
pass
class LinuxFactory(GUIFactory):
def create_button(self):
return LinuxButton()
class WindowsFactory(GUIFactory):
def create_button(self):
return WindowsButton()
class MacOSFactory(GUIFactory):
def create_button(self):
return MacOSButton()
if platform == "linux":
factory = LinuxFactory()
elif platform == "darwin":
factory = MacOSFactory()
elif platform == "win32":
factory = WindowsFactory()
else:
raise NotImplementedError(f"Not implemented for your platform: {platform}")
button = factory.create_button()
result = button.paint()
print(result)
Alternatywna implementacja wykorzystująca same klasy jako fabryki:
from abc import ABC, abstractmethod
from sys import platform
class Button(ABC):
@abstractmethod
def paint(self):
pass
class LinuxButton(Button):
def paint(self):
return "Render a button in a Linux style"
class WindowsButton(Button):
def paint(self):
return "Render a button in a Windows style"
class MacOSButton(Button):
def paint(self):
return "Render a button in a MacOS style"
if platform == "linux":
factory = LinuxButton
elif platform == "darwin":
factory = MacOSButton
elif platform == "win32":
factory = WindowsButton
else:
raise NotImplementedError(f"Not implemented for your platform: {platform}")
button = factory()
result = button.paint()
print(result)
Zobacz też
Bibliografia
Linki zewnętrzne
- Implementacja Fabryki Abstrakcyjnej w Javie
- Multimedia związane z fabryką abstraktów w Wikimedia Commons
- Diagram Abstrakcyjnej Fabryki UML + formalna specyfikacja w LePUS3 i Class-Z (język opisu projektu)
- Przykład implementacji Fabryki Abstraktów