Wzorzec strategii działa dobrze, aby uniknąć dużych, jeśli ... innych konstrukcji i ułatwić dodawanie lub zastępowanie funkcjonalności. Jednak moim zdaniem nadal ma to jedną wadę. Wygląda na to, że w każdej implementacji wciąż musi istnieć rozgałęziony konstrukt. Może to być plik fabryczny lub plik danych. Jako przykład weź system zamawiania.
Fabryka:
// All of these classes implement OrderStrategy
switch (orderType) {
case NEW_ORDER: return new NewOrder();
case CANCELLATION: return new Cancellation();
case RETURN: return new Return();
}
Kod po tym nie musi się martwić, a teraz jest tylko jedno miejsce, aby dodać nowy typ zamówienia, ale ta sekcja kodu wciąż nie jest rozszerzalna. Wyciągnięcie go do pliku danych nieco poprawia jego czytelność (dyskusyjne, wiem):
<strategies>
<order type="NEW_ORDER">com.company.NewOrder</order>
<order type="CANCELLATION">com.company.Cancellation</order>
<order type="RETURN">com.company.Return</order>
</strategies>
Ale to wciąż dodaje kod podstawowy do przetwarzania pliku danych - przyznany, łatwiejszy do testowania jednostkowego i stosunkowo stabilny kod, ale dodatkowa złożoność.
Ponadto tego rodzaju konstrukcja nie testuje dobrze integracji. Każda indywidualna strategia może być teraz łatwiejsza do przetestowania, ale każda nowa strategia, którą dodajesz, dodatkowo komplikuje testowanie. Jest mniej, niż byś zrobił, gdybyś nie użył wzoru, ale wciąż tam jest.
Czy istnieje sposób na wdrożenie wzorca strategii łagodzącego tę złożoność? A może jest to tak proste, jak to tylko możliwe, a próba posunięcia się do przodu dodałaby kolejną warstwę abstrakcji, która przyniosłaby niewiele korzyści?
źródło
eval
... może nie działać w Javie, ale może w innych językach?Odpowiedzi:
Oczywiście nie. Nawet jeśli użyjesz kontenera IoC, będziesz musiał gdzieś mieć warunki, decydując, którą konkretną implementację wprowadzić. Taka jest natura wzorca strategii.
Naprawdę nie rozumiem, dlaczego ludzie myślą, że to problem. W niektórych książkach, takich jak Refaktoryzacja Fowlera , znajdują się stwierdzenia, że jeśli widzisz przełącznik / przypadek lub łańcuch if / els w środku innego kodu, powinieneś rozważyć ten zapach i spróbować przenieść go na własną metodę. Jeśli kod w każdym przypadku jest dłuższy niż wiersz, może dwa, powinieneś rozważyć uczynienie tej metody Metodą Fabryczną, zwracającą Strategie.
Niektóre osoby przyjęły to do wiadomości, że przełącznik / obudowa jest zła. Nie o to chodzi. Ale jeśli to możliwe, powinien znajdować się sam.
źródło
Tak , za pomocą HashMap / słownik gdzie co rejestry w realizacji Strategii. Metoda fabryczna stanie się podobna
Każda implementacja strategii musi rejestrować fabrykę z jej typem zamówienia i informacjami dotyczącymi tworzenia klasy.
Możesz użyć konstruktora statycznego do rejestracji, jeśli Twój język to obsługuje.
Metoda register robi nic więcej niż dodawanie nowej wartości do mapy hash:
[aktualizacja 2012-05-04]
To rozwiązanie jest znacznie bardziej złożone niż oryginalne „rozwiązanie przełączające”, które wolałbym przez większość czasu.
Jednak w środowisku, w którym strategie często się zmieniają (tj. Kalkulacja ceny w zależności od klienta, czasu, ...), to rozwiązanie z mapą zbiorczą w połączeniu z kontenerem IoC może być dobrym rozwiązaniem.
źródło
factory.register(NEW_ORDER, NewOrder.class);
czystsze, a mniej naruszenie OCP, niż rzędycase NEW_ORDER: return new NewOrder();
?„Strategia” polega na tym, że przynajmniej raz trzeba wybierać między alternatywnymi algorytmami . Gdzieś w twoim programie ktoś musi podjąć decyzję - może użytkownik lub twój program. Jeśli użyjesz IoC, refleksji, ewaluatora pliku danych lub konstrukcji przełącznika / skrzynki do implementacji, nie zmieni to sytuacji.
źródło
Wzorzec strategii jest używany, gdy określasz możliwe zachowania, i najlepiej jest go używać podczas wyznaczania procedury obsługi podczas uruchamiania. Określanie, którego wystąpienia strategii można użyć za pośrednictwem Mediatora, standardowego kontenera IoC, niektórych fabryk takich jak opisujesz, lub po prostu używając właściwego opartego na kontekście (ponieważ często strategia jest dostarczana jako część szerszego zastosowania klasy, która ją zawiera).
W przypadku tego rodzaju zachowania, w którym należy wywoływać różne metody w oparciu o dane, sugerowałbym użycie konstrukcji polimorfizmu dostarczanych przez dany język; nie wzór strategii.
źródło
Rozmawiając z innymi programistami, w których pracuję, znalazłem inne interesujące rozwiązanie - specyficzne dla Javy, ale jestem pewien, że pomysł działa w innych językach. Skonstruuj wyliczenie (lub mapę, nie ma to większego znaczenia, które) za pomocą odwołań do klas i utwórz instancję za pomocą odbicia.
To drastycznie zmniejsza kod fabryczny:
(Proszę zignorować słabą obsługę błędów - przykładowy kod :))
Nie jest to najbardziej elastyczne, ponieważ kompilacja jest nadal wymagana ... ale redukuje zmianę kodu do jednej linii. Podoba mi się, że dane są oddzielone od kodu fabrycznego. Możesz nawet wykonywać takie czynności, jak ustawianie parametrów albo za pomocą klas abstrakcyjnych / interfejsów w celu zapewnienia wspólnej metody lub tworzenia adnotacji w czasie kompilacji w celu wymuszenia podpisów określonych konstruktorów.
źródło
Rozszerzalność można poprawić, każąc każdej klasie zdefiniować własny typ zamówienia. Następnie twoja fabryka wybiera tę, która pasuje.
Na przykład:
źródło
Myślę, że powinien istnieć limit liczby dopuszczalnych gałęzi.
Na przykład, jeśli w instrukcji switch znajduje się więcej niż osiem przypadków, to ponownie oceniam kod i szukam tego, co mogę ponownie uwzględnić. Dość często stwierdzam, że niektóre przypadki można zgrupować w osobną fabrykę. Zakładam tutaj, że istnieje fabryka, która buduje strategie.
Tak czy inaczej, nie możesz tego uniknąć i gdzieś będziesz musiał sprawdzić stan lub typ obiektu, z którym pracujesz. Następnie zbudujesz dla tego strategię. Dobry stary podział problemów.
źródło