Abstrakcyjny wzór fabryki - Abstract factory pattern

Diagram klas UML

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, DocumentCreatorktóra udostępnia interfejsy do tworzenia wielu produktów (np. createLetter()i createResume()). System miałby dowolną liczbę pochodnych konkretnych wersji DocumentCreatorklasy, takich jak FancyDocumentCreatorlub ModernDocumentCreator, z których każda miałaby inną implementację createLetter()i createResume()która tworzyłaby odpowiedni obiekt, taki jak FancyLetterlub ModernResume. Każdy z tych produktów pochodzi z prostej abstrakcyjnej klasy, takiej jak Letterlub Resumektórej klient jest świadomy. Kod klient dostanie odpowiednią instancję z poniższych DocumentCreatori wywołać jego metody fabrycznych . Każdy z wynikowych obiektów byłby stworzony z tej samej DocumentCreatorimplementacji i miałby wspólny motyw (wszystkie byłyby fantazyjnymi lub nowoczesnymi obiektami). Klient musiałby tylko wiedzieć, jak obsłużyć abstrakt Letterlub Resumeklasę, 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

Przykład diagramu klas Metoda createButton w interfejsie GUIFactory zwraca obiekty typu Button.  Zwracana implementacja Button zależy od tego, która implementacja GUIFactory obsługuje wywołanie metody.
Przykład schemat klasy Sposób createButtonod GUIFactoryzwrotów interfejsu obiekty typu Button. To, jaka implementacja Buttonjest zwracana, zależy od tego, która implementacja GUIFactoryobsługuje wywołanie metody.
Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Fabryka abstrakcyjna.  [8]
Przykładowy diagram klas i sekwencji UML dla wzorca projektowego Fabryka abstrakcyjna.

Na powyższym diagramie klas UML klasa , Clientktóra wymaga ProductAi ProductBobiekty nie tworzy bezpośrednio instancji klasy ProductA1i ProductB1. Zamiast tego Clientodnosi się do AbstractFactoryinterfejsu do tworzenia obiektów, który Clientuniezależnia sposób tworzenia obiektów (które konkretnie klasy są tworzone). Factory1Klasa implementuje AbstractFactoryinterfejs poprzez utworzenie wystąpienia ProductA1i ProductB1zajęcia.
W UML Schemat sekwencji pokazuje interakcje Run-Time: Clientcalls obiektu createProductA()na Factory1obiekt, który tworzy i zwraca ProductA1obiekt. Następnie Clientwywołuje createProductB()on Factory1, który tworzy i zwraca ProductB1obiekt.

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