Używanie wzorca strategii i wzorca poleceń

121

Oba wzorce projektowe hermetyzują algorytm i oddzielają szczegóły implementacji od ich klas wywołujących. Jedyną różnicą, jaką mogę dostrzec, jest to, że wzorzec Strategy przyjmuje parametry do wykonania, podczas gdy wzorzec Command nie.

Wydaje mi się, że wzorzec polecenia wymaga, aby wszystkie informacje do wykonania były dostępne, gdy jest tworzony, i jest w stanie opóźnić jego wywołanie (być może jako część skryptu).

Jakie wskazówki wskazują, czy użyć jednego wzorca, czy drugiego?

Extrakun
źródło

Odpowiedzi:

94

Dołączam tabelę hierarchii hermetyzacji kilku wzorców projektowych GoF, aby pomóc wyjaśnić różnice między tymi dwoma wzorcami. Mam nadzieję, że lepiej ilustruje to, co zawiera każdy z nich, więc moje wyjaśnienie ma więcej sensu.

Po pierwsze, hierarchia zawiera zakres, do którego ma zastosowanie dany wzorzec lub odpowiedni wzorzec, który ma zostać użyty do hermetyzacji pewnego poziomu szczegółowości, w zależności od tego, od której strony tabeli zaczynasz.

tabela hierarchii hermetyzacji wzorca projektowego

Jak widać z tabeli, obiekt Strategy Pattern ukrywa szczegóły implementacji algorytmu, więc użycie innego obiektu strategii będzie wykonywać tę samą funkcjonalność, ale w inny sposób. Każdy obiekt strategii może być zoptymalizowany pod kątem konkretnego czynnika lub działać na jakimś innym parametrze; a dzięki zastosowaniu wspólnego interfejsu kontekst może bezpiecznie współpracować z każdym z nich.

Wzorzec poleceń zawiera znacznie mniejszy poziom szczegółowości niż algorytm. Koduje szczegóły potrzebne do wysłania wiadomości do obiektu: odbiornik, selektor i argumenty. Zaletą zobiektywizowania tak niewielkiej części wykonania procesu jest to, że takie komunikaty mogą być wywoływane w różnych punktach czasu lub lokalizacji w ogólny sposób bez konieczności kodowania ich szczegółów. Pozwala na wywoływanie wiadomości jeden lub więcej razy lub przekazywanie ich do różnych części systemu lub wielu systemów bez konieczności poznania szczegółów konkretnego wywołania przed wykonaniem.

Jak to jest typowe dla wzorców projektowych, nie wymagają one, aby wszystkie implementacje były identyczne w szczegółach, aby nosić nazwę wzoru. Szczegóły mogą się różnić w implementacji oraz w tym, jakie dane są zakodowane w obiekcie w porównaniu z argumentami metody.

Huperniketes
źródło
Więc gdybym miał system, który filtrował wyniki za pomocą „potoku filtru” i używał delegatów jako filtrów (gdzie każdy z algorytmów filtru byłby hermetyzowany w funkcji), czy byłby to wzorzec polecenia? W tym przypadku widzę delegata funkcji filtru jako zapewniającego rodzaj kontraktu określający, do czego każdy filtr musi się stosować pod względem danych wejściowych i wyjściowych.
KTF
4
@KTF, nie. Wzorzec Command wykorzystuje obiekt, który ma większość (jeśli nie wszystkie) informacji potrzebnych (np. Odbiornik, selektor, argumenty) do wywołania metody obiektu. Jest to uproszczony wzorzec, który można wykorzystać w innych wzorcach projektowych, takich jak Łańcuch odpowiedzialności, Kolekcja i wzór potoku, który opisujesz. „Rodzaj umowy” dostarczony przez twoich delegatów to kolejny wzorzec, interfejs.
Huperniketes
50

Strategie obejmują algorytmy. Polecenia oddzielają nadawcę od odbiorcy żądania, zamieniają żądanie w obiekt.

Jeśli to algorytm, jak coś zostanie zrobione, użyj strategii. Jeśli chcesz oddzielić wywołanie metody od jej wykonania, użyj Command. Polecenia są często używane podczas kolejkowania wiadomości do późniejszego wykorzystania, na przykład zadania lub transakcji.

Paul Rubel
źródło
to miało sens en.wikipedia.org/wiki/Command_Pattern, klient i wywołujący są ze sobą powiązane, ale jednocześnie nie wiedzą o sobie!
Kalpesh Soni
26

Odpowiadając na bardzo stare pytanie. (czy ktoś widzi najnowsze odpowiedzi zamiast większości głosów?)

Jest to uzasadnione zamieszanie z powodu podobieństw. Zarówno strategie, jak i polecenia wykorzystują hermetyzację . Ale to nie czyni ich takimi samymi.

Kluczową różnicą jest zrozumienie, co jest hermetyzowane. Zasada OO, od której zależą oba wzorce, to Hermetyzuj to, co się zmienia .

W przypadku strategii różni się algorytm . Na przykład jeden obiekt strategii wie, jak wyprowadzić dane do pliku XML, podczas gdy drugi wyprowadza, powiedzmy, JSON. Różne algorytmy są przechowywane ( zamknięte ) w różnych klasach. To takie proste.

W przypadku polecenia, to, co się różni, to samo żądanie . Żądanie może pochodzić z adresu File Menu > Deletelub Right Click > Context Menu > Deletelub Just Delete Button pressed. We wszystkich trzech przypadkach można wygenerować 3 obiekty poleceń tego samego typu. Te obiekty poleceń reprezentują tylko 3 żądania usunięcia; nie algorytm usuwania. Ponieważ żądania to teraz kilka obiektów, moglibyśmy łatwo nimi zarządzać. Nagle zapewnienie funkcji, takich jak cofanie lub ponawianie, stało się trywialne.

Nie ma znaczenia, w jaki sposób polecenie implementuje żądaną logikę. Podczas wywoływania funkcji execute () może zaimplementować algorytm wyzwalający usunięcie lub nawet delegować go do innych obiektów, a nawet delegować do strategii. To tylko szczegół implementacji wzorca polecenia. Dlatego nazywa się go poleceniem, chociaż nie jest to uprzejmy sposób na żądanie : -)

Porównaj to ze strategią; ten wzorzec dotyczy tylko rzeczywistej logiki, która jest wykonywana. Jeśli to zrobimy, pomoże to osiągnąć różne kombinacje zachowań przy minimalnym zestawie klas, zapobiegając w ten sposób eksplozji klas.

Myślę, że Command pomaga nam poszerzyć naszą wiedzę na temat hermetyzacji, podczas gdy strategia zapewnia naturalne wykorzystanie hermetyzacji i polimorfizmu.

rpattabi
źródło
15

Sposób, w jaki na to patrzę, jest taki, że masz wiele sposobów robienia tego samego, każdy z nich jest strategią, a coś w czasie wykonywania określa, która strategia zostanie wykonana.

Może najpierw wypróbuj StrategyOne, jeśli wyniki nie są wystarczająco dobre, wypróbuj StrategyTwo ...

Polecenia są powiązane z różnymi rzeczami, które muszą się wydarzyć, na przykład TryToWalkAcrossTheRoomCommand. To polecenie zostanie uruchomione, gdy jakiś obiekt spróbuje przejść przez pokój, ale w środku może wypróbować StrategyOne i StrategyTwo, próbując przejść przez pokój.

znak

MStodd
źródło
2
RE: „wiele sposobów robienia tego samego” - wydaje się to sprzeczne z niektórymi typowymi przykładami strategii. Szczególnie te, w których istnieją klasy implementacyjne, które wykonują dodawanie, odejmowanie, mnożenie itp. Może to nie są dobre przykłady?
Joshua Davis,
1
@JoshuaDavis wszystkie te „substratagi” są specjalnymi przypadkami jednej strategii: operacji arytmetycznej . mają wspólne argumenty (2 operandy) i jako wynik dają jedną wartość. robiąc to samo (bycie czarnymi skrzynkami) na swój własny sposób, w zależności od implementacji. więc nie widzę tutaj konfliktu, ale wręcz przeciwnie: ładny przykład =)
jungle_mole
7

Moim zdaniem mogę się mylić, ale traktuję polecenie jako funkcję do wykonania lub reakcję. Powinno być co najmniej dwóch graczy: ten, który żąda akcji, i ten, który ją wykonuje. GUI jest typowym przykładem dla wzorca poleceń:

  • Wszystkie przyciski na pasku narzędzi aplikacji są powiązane z jakąś czynnością.
  • Button jest w tym przypadku executorem.
  • Akcja jest w tym przypadku poleceniem.

Polecenie jest zwykle ograniczone do jakiegoś zakresu lub obszaru biznesowego, ale nie jest konieczne: możesz mieć polecenia, które wystawiają rachunek, uruchamiają rakietę lub usuwają plik implementujący ten sam interfejs (np. Pojedynczą execute()metodę) w jednej aplikacji. Często polecenia zawierają się same, więc nie potrzebują niczego od wykonawcy do przetworzenia zadania, do którego mają zamiar (wszystkie niezbędne informacje są podawane w czasie tworzenia), czasami polecenia są zależne od kontekstu i powinny być w stanie wykryć ten kontekst ( Polecenie Backspace powinno znać pozycję kursora w tekście, aby poprawnie usunąć poprzedni znak; Polecenie wycofania powinno wykryć bieżącą transakcję do wycofania; ...).

Strategia jest nieco inna: to jest bardziej związana z pewnym obszarze. Strategia może definiować regułę formatowania daty (w UTC? Specyficzne dla ustawień regionalnych?) (Strategia „formatowania daty”) lub obliczania kwadratu dla figury geometrycznej (strategia „kalkulatora kwadratu”). Strategie są w tym sensie obiektami wagi lotnej, które przyjmują dane jako dane wejściowe („data”, „figura”,…) i na ich podstawie podejmują jakąś decyzję. Być może nie najlepszy, ale dobrym przykładem strategii jest ta związana z javax.xml.transform.Sourceinterfejsem: w zależności od tego, czy przekazany obiekt jest, DOMSourceczy SAXSourceteż StreamSourcestrategia (w tym przypadku = transformator XSLT) zastosuje inne reguły do ​​jego przetworzenia. Wdrożenie może być proste switchlub obejmować wzorzec Łańcucha odpowiedzialności .

Ale rzeczywiście jest coś wspólnego między tymi dwoma wzorcami: polecenia i strategie zawierają algorytmy w tym samym obszarze semantycznym.

dma_k
źródło
1
Polecenie traktuję jako funkcję zwrotną lub reakcję. Powinno być co najmniej dwóch graczy: jeden żądający akcji i jeden wykonujący ... - Rozumiem, co próbujesz powiedzieć, ale unikałbym słowa „oddzwonienie”, ponieważ często to słowo „callback” oznacza wywołanie asynchroniczne i nie musisz wykonywać wywołań asynchronicznych, aby wzorzec polecenia był użyteczny. Przykład: Microsoft Word. Kliknięcia przycisków na pasku narzędzi i naciśnięcia klawiszy skrótów nie wywołują poleceń asynchronicznych, ale możemy docenić, jak wzorzec poleceń byłby przydatny w tym przypadku
Jim G.
Zgadzam się, chociaż jak powiedział Jim, dokonałbym edycji, aby usunąć odniesienie do wywołania zwrotnego.
JARC
Dzięki, zrobiłem kilka rozszerzeń. Daj mi znać, jeśli się zgadzasz / nie zgadzasz.
dma_k,
5

Komenda:

Podstawowe składniki:

  1. Polecenie deklaruje interfejs dla abstrakcyjnych poleceń, takich jakexecute()
  2. Odbiornik wie, jak wykonać określone polecenie
  3. Invoker posiada ConcreteCommand , który musi zostać wykonany
  4. Klient tworzy ConcreteCommand i przypisuje Odbiorcę
  5. ConcreteCommand definiuje powiązanie między Command i Receiver

Przepływ pracy:

Klient wywołuje Invoker => Invoker wywołuje ConcreteCommand => ConcreteCommand wywołuje metodę Receiver , która implementuje abstrakcyjną metodę Command .

Zaleta : Klient nie ma wpływu na zmiany w poleceniu i odbiorniku. Invoker zapewnia luźne powiązanie między klientem a odbiorcą. Możesz uruchomić wiele poleceń z tym samym Invokerem.

Wzorzec poleceń pozwala na wykonanie polecenia na różnych odbiornikach przy użyciu tego samego Invokera . Invoker nie zna typu odbiorcy

Aby lepiej zrozumieć koncepcje, zapoznaj się z tym artykułem w JournalDev autorstwa Pankaja Kumara i artykule dzone autorstwa Jamesa Sugrue, oprócz linku do Wikipedii.

Możesz użyć wzorca polecenia , aby

  1. Odłącz wywołującego i odbiorcę polecenia

  2. Zaimplementuj mechanizm wywołania zwrotnego

  3. Wdrażaj funkcje cofania i ponawiania

  4. Prowadź historię poleceń

java.lang.Threadjest jedną dobrą implementacją wzorca polecenia . Możesz traktować Thread jako wywołującego i klasę implementującą Runnable jako ConcreteCommonad / Receiver, a run()metodę jako Command .

Wersja wzorca poleceń Cofnij / Ponów można przeczytać w artykule Theodore Norvell

Strategia:

Wzorzec strategii jest bardzo łatwy do zrozumienia. Użyj tego wzoru, gdy

Istnieje wiele implementacji algorytmu, a implementacja algorytmu może ulec zmianie w czasie wykonywania w zależności od określonych warunków .

Weźmy na przykład składnik Opłaty w systemie rezerwacji linii lotniczych

Linie lotnicze chciałyby oferować różne taryfy w różnych okresach - w miesiącach szczytu i poza nim. W pozaszczytowe dni w podróży chce stymulować popyt, oferując atrakcyjne rabaty.

Kluczowe wnioski dotyczące wzorca strategii :

  1. To wzorzec zachowania
  2. Opiera się na delegacji
  3. Zmienia wnętrzności obiektu poprzez modyfikację zachowania metody
  4. Służy do przełączania się między rodziną algorytmów
  5. Zmienia zachowanie obiektu w czasie wykonywania

Powiązane posty z przykładami kodu:

Używanie wzorca Command Design

Przykład wzorca strategii ze świata rzeczywistego

Ravindra babu
źródło
0

Dla mnie różnica polega na intencji. Implementacje obu wzorców są dość podobne, ale mają różne cele:

  • W przypadku strategii komponent korzystający z obiektu wie, co robi obiekt (i użyje go do wykonania części własnej pracy), ale nie obchodzi go, jak to robi.

  • W przypadku polecenia komponent używający obiektu nie wie, co robi polecenie, ani jak to robi - po prostu wie, jak je wywołać. Zadaniem dzwoniącego jest po prostu uruchomienie polecenia - przetwarzanie wykonywane przez polecenie nie stanowi części podstawowej pracy dzwoniącego.

Na tym polega różnica - czy obiekt korzystający z komponentu faktycznie wie lub dba o to, co robi komponent? W większości przypadków można to ustalić na podstawie tego, czy obiekt wzorca zwraca wartość do swojego wywołującego. Jeśli wywołujący dba o to, co robi obiekt wzorca, prawdopodobnie będzie chciał, aby coś zwrócił i będzie to strategia. Jeśli nie dba o jakąkolwiek zwracaną wartość, prawdopodobnie jest to Polecenie (uwaga, coś w rodzaju wywoływanego języka Java jest nadal poleceniem, ponieważ chociaż zwraca wartość, wywołujący nie dba o wartość - po prostu przekazuje ją z powrotem do tego, co pierwotnie dostarczało polecenie).

BarrySW19
źródło