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?
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?
Odpowiedzi:
Tradycyjna zorientowana obiektowo mądrość polega na unikaniu if
instrukcji 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ę if
instrukcji. 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
.
if
s. Zobacz odpowiedź @ BЈовић na prosty przykład, jak to osiągnąć. Doceniony.
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ć.
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:
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.
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 if
stwierdzenia. Może odczytać konkretny typ fabryki z pliku konfiguracyjnego, wyprowadzić go ze struktury mapy itp.
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.
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.
Wszystko zależy od tego, jak to zaimplementujesz. Możesz użyć std::map
do 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.