Czy wzór fabryczny narusza zasadę otwartej / zamkniętej?

14

Dlaczego ten ShapeFactory używa instrukcji warunkowych do określenia, który obiekt ma zostać utworzony. Czy nie musimy modyfikować ShapeFactory, jeśli chcemy dodać inne klasy w przyszłości? Dlaczego nie narusza to zasady otwartego zamkniętego?

Projekt fabryczny

ShapeFactory Design

Armon Safai
źródło
2
Do którego „wzorca fabrycznego” chodzi dokładnie? Ogólnie rzecz biorąc, fabryka to dowolny obiekt lub metoda służąca do tworzenia wystąpienia obiektu. Następnie istnieją konkretne odmiany tego ogólnego pomysłu, takie jak abstrakcyjny wzorzec fabryki, w którym każda instancja fabryki reprezentuje określoną paletę wyborów - zwykle zarządzaną za pomocą podklasy, a nie warunków.
amon
3
Dzięki za te informacje, to bardzo dużo wyjaśnia. Jest to definitywnie przykład wzorca fabrycznego, ale nie abstrakcyjnego wzorca fabrycznego powszechnie kojarzonego z wzorami fabrycznymi. Kod w tym artykule jest dość wątpliwy i nie spodziewałbym się, że zobaczę coś takiego w prawdziwym kodzie.
amon
@ArmonSafai: Często łączysz ten post na blogu, ale tak naprawdę nie wyjaśniasz dlaczego. Czy wszyscy jesteśmy w jakiś sposób nieświadomi wzoru? Mamy również Google, tak jak Ty.
Robert Harvey,
1
@RobertHarvey Łączę ten post na blogu, aby pokazać, w jaki sposób wzór fabryczny na tej stronie używa warunkowych
Armon Safai,

Odpowiedzi:

20

Tradycyjna zorientowana obiektowo mądrość polega na unikaniu ifinstrukcji i zastępowaniu ich dynamiczną wysyłką przesłoniętych metod w podklasach klasy abstrakcyjnej. Jak na razie dobrze.

Ale celem wzoru fabrycznego jest odciążenie cię od konieczności posiadania wiedzy o poszczególnych podklasach i pracy tylko z abstrakcyjną nadklasą . Chodzi o to, że fabryka wie lepiej od ciebie, którą konkretną klasę utworzyć, i będziesz lepiej pracować tylko z metodami opublikowanymi przez superklasę. Jest to często prawda i cenny wzór.

Dlatego nie ma mowy, aby napisanie klasy fabrycznej mogło zrzec się ifinstrukcji. Przesunąłoby to ciężar wyboru konkretnej klasy na osobę wywołującą, czego dokładnie powinien unikać ten wzorzec. Nie wszystkie zasady są bezwzględne (w rzeczywistości żadna zasada nie jest absolutna), a jeśli użyjesz tego wzorca, założysz, że korzyść z niego jest większa niż korzyść z niestosowania if.

Kilian Foth
źródło
2
Zupełnie możliwe jest utworzenie wzoru fabrycznego bez wielu ifs. Zobacz odpowiedź @ BЈовић na prosty przykład, jak to osiągnąć. Doceniony.
David Arno,
11
@DavidArno Oczywiście istnieją różne sposoby wyboru konkretnej klasy. Lokalizator usług to jeden, a konfigurowalny kontener IoC to drugi. To tylko szczegóły implementacji; nie umniejszają głównego przesłania Killian, a mianowicie, że Fabryka zwalnia rozmówcę z konieczności decydowania, która konkretna klasa ma zostać utworzona. Nie daj się zanurzyć w szczegóły.
Robert Harvey,
1
Wspaniałe stwierdzenie, które w żaden sposób nie odpowiada na pytanie.
Martin Maat,
1
@ R. Schmitz Myślę, że masz błędne założenia. Myślę, że wiele osób przeoczyło to pytanie z PO: „Czy nie musimy modyfikować ShapeFactory, jeśli chcemy dodać inne klasy w przyszłości?” WYRAŹNIE, OP jest zdezorientowany, myśląc, że ten wzór narusza OCP, ponieważ aby dodać nową funkcjonalność, musisz zmodyfikować istniejący kod. Prawidłowa odpowiedź na to pytanie znajduje się w mojej odpowiedzi. Krótka odpowiedź: zostawiasz ten kod w spokoju i stosujesz wzorzec Abstract Factory w celu ROZSZERZENIA (nie modyfikowania) istniejącej funkcjonalności. Z powodu tej odpowiedzi Kilian NIE odnosi się do pytania.
hfontanez
5

W tym przykładzie prawdopodobnie użyto instrukcji warunkowej, ponieważ jest najprostsza. Bardziej złożona implementacja może wymagać mapy lub konfiguracji lub (jeśli chcesz być naprawdę fantazyjny) jakiegoś rejestru, w którym klasy mogą się zarejestrować. Jednak nie ma nic złego w korzystaniu z warunkowej, jeśli liczba klas jest niewielka i zmienia się rzadko.

Rozszerzenie warunku w celu dodania wsparcia dla nowej podklasy w przyszłości byłoby w istocie ściśle pogwałceniem zasady otwartej / zamkniętej. „Prawidłowym” rozwiązaniem byłoby stworzenie nowej fabryki z tym samym interfejsem. To powiedziawszy, przestrzeganie zasady O / C powinno być zawsze porównywane z innymi zasadami projektowania, takimi jak KISS i YAGNI.

To powiedziawszy, wyświetlany kod jest wyraźnie przykładowym kodem, który został zaprojektowany, aby pokazać koncepcję fabryki i nic więcej. Np. Zwracanie wartości null jak w przykładzie jest naprawdę kiepskim stylem, ale bardziej skomplikowana obsługa błędów po prostu zaciemniłaby sens. Przykładowy kod nie jest kodem jakości produkcji, nie należy się go spodziewać.

JacquesB
źródło
Czy możesz wyjaśnić, jak działałaby mapa / konfiguracja / rejestr?
Armon Safai
Samorejestrujące się fabryki są, AFAIK, niemożliwe w C ++ w bibliotekach statycznych, ponieważ nieużywane (tj. Używane odry) zmienne globalne są odrzucane przez łańcuchy narzędzi.
void.pointer
@ArmonSafai przeczytaj to, aby lepiej zrozumieć goo.gl/RYuNSM
AZ_
2

Sam wzór nie narusza zasady otwartej / zamkniętej (OCP). Jednak naruszamy OCP, gdy nieprawidłowo używamy wzorca.

Prosta odpowiedź na to pytanie jest następująca:

  1. Stwórz swoją podstawową funkcjonalność za pomocą Factory Method Pattern.
  2. ZWIĘKSZ swoją funkcjonalność za pomocą abstrakcyjnego wzorca fabrycznego

W podanym przykładzie podstawowa funkcjonalność obsługuje trzy kształty: koło, prostokąt i kwadrat. Załóżmy, że w przyszłości musisz obsługiwać Triangle, Pentagon i Hexagon. Aby to zrobić BEZ naruszania OCP, musisz utworzyć dodatkową fabrykę, która będzie obsługiwać twoje nowe kształty (nazwijmy AdvancedShapeFactory), a następnie użyj AbstractFactory, aby zdecydować, którą fabrykę musisz stworzyć, aby stworzyć potrzebne kształty.

hfontanez
źródło
Zdecydowanie wolę samorejestrujące się rozwiązanie dla fabryk (w przypadku braku prawdziwego, konfigurowalnego kontenera IoC, który jest najlepszy), ponieważ w przeciwnym razie otrzymujemy z Twojej propozycji zasadniczo fabryki fabryk i wtedy sprawy stają się zbyt skomplikowane.
Nom1fan
1

Jeśli mówisz o wzorcu fabryki abstrakcyjnej, podejmowanie decyzji często nie odbywa się w samej fabryce, ale w kodzie aplikacji. To ten kod wybiera konkretną fabrykę, która ma zostać utworzona i przekazana do kodu klienta, który będzie korzystał z obiektów wytworzonych przez fabrykę. Zobacz koniec przykładu Java tutaj: https://en.wikipedia.org/wiki/Abstract_factory_pattern

Podejmowanie decyzji niekoniecznie oznacza ifstwierdzenia. Może odczytać konkretny typ fabryki z pliku konfiguracyjnego, wyprowadzić go ze struktury mapy itp.

guillaume31
źródło
Jeśli ja, osoba dzwoniąca, podejmuję decyzję, którą konkretną klasę utworzyć, to dlaczego zawracam sobie głowę fabryką abstrakcyjną?
Robert Harvey,
Proszę zdefiniować „dzwoniącego”. Jak opisuję w mojej odpowiedzi, istnieje globalny kod aplikacji, a następnie kod, który musi spawnować obiekty za pomocą Factory. Podczas gdy ten ostatni musi być nieświadomy konkretnej klasy, aby utworzyć instancję, jakiś inny kod kontekstowy musi się o tym dowiedzieć i nowi ...
guillaume31,
0

Jeśli myślisz o Open-Close na poziomie klasy w tej fabryce, tworzysz inną klasę w twoim systemie Open-Close, na przykład, jeśli masz inną klasę, która przyjmuje jeden Kształt i oblicza powierzchnię (typowy przykład), ta klasa jest OpenClose, ponieważ może obliczyć powierzchnię dla nowych typów kształtów bez modyfikacji. Następnie masz inną klasę, która rysuje kształt, inną klasę, która przyjmuje N kształty i zwraca większą i możesz ogólnie myśleć, że inne klasy w twoim systemie, które zajmują się kształtami, to Open-Close (przynajmniej o kształtach). Patrząc na projekt, fabryka pozwala pozostałej części systemu na otwieranie i zamykanie, a poza tym sama fabryka NIE JEST otwarci-zamykanie.

Oczywiście można również otworzyć tę fabrykę za pomocą dynamicznego ładowania, a cały system może być otwarty-zamknięty (możesz dodawać nowe kształty, upuszczając słój na ścieżkę klasy). Musisz ocenić, czy ta dodatkowa złożoność jest warta w zależności od systemu, który budujesz, nie wszystkie systemy wymagają funkcji wtykowych i nie cały system musi być całkowicie otwarty-zamknięty.

AlfredoCasado
źródło
0

Zasada otwartego zamkniętego, podobnie jak zasada podstawienia Liskowa, ma zastosowanie do drzew klas, do hierarchii dziedziczenia. W twoim przykładzie klasa fabryczna nie znajduje się w drzewie genealogicznym klas, które tworzy, więc nie może naruszać tych reguł. Wystąpiłoby naruszenie, gdyby Twój GetShape (lub bardziej trafnie nazwany, CreateShape) został zaimplementowany w klasie bazowej Shape.

Martin Maat
źródło
-2

Wszystko zależy od tego, jak to zaimplementujesz. Możesz użyć std::mapdo przechowywania wskaźników funkcji do funkcji tworzących obiekty. Wtedy zasada otwarcia / zamknięcia nie zostanie naruszona. Lub przełącznik / obudowa.

W każdym razie, jeśli nie podoba ci się wzór fabryczny, zawsze możesz użyć zastrzyku zależności.

BЈовић
źródło
6
W jaki sposób przełącznik / obudowa jest lepszy od warunków warunkowych? Używanie mapy / dict / table do reprezentowania kodu jako danych jest dobre, jeśli faktycznie potrzebujesz rejestru różnych implementacji - np. W niektórych implementacjach kontenera DI. Ale posiadanie różnych wywołań zwrotnych tego samego typu nie jest konieczne w większości fabryk! Nie bardzo rozumiem, dlaczego to sugerujesz. Ponadto wiele kontenerów DI jest implementowanych w kategoriach obiektów fabrycznych, więc sugerowanie używania DI zamiast fabryk wydaje się nieco okrągłe.
amon
1
@amon Chciałem użyć innych rodzajów DI - nie fabryki.
BЈовић
1
Jak twoja fabryka zdecyduje, którego wskaźnika użyć? W końcu musisz podjąć decyzję.
whatsisname
@ArmonSafai ???
BЈовић