2008-10-05 18 views
12

Zerknąłem na this explanation on Wikipedia, w szczególności na próbkę C++, i nie rozpoznałem różnicy między samym zdefiniowaniem 3 klas, tworzeniem instancji i wywoływaniem ich oraz tego przykładu. To, co zobaczyłem, polegało na umieszczeniu dwóch innych klas w procesie i nie było widać, gdzie byłaby korzyść. Teraz jestem pewien, że brakuje mi czegoś oczywistego (drewno dla drzew) - czy ktoś mógłby wyjaśnić to za pomocą ostatecznego przykładu w świecie rzeczywistym?Gdzie jest korzyść z używania wzorca strategii?


Co mogę zrobić z odpowiedzi tak daleko, wydaje mi się po prostu bardziej skomplikowany sposób to zrobić:

have an abstract class: MoveAlong with a virtual method: DoIt() 
have class Car inherit from MoveAlong, 
    implementing DoIt() { ..start-car-and-drive..} 
have class HorseCart inherit from MoveAlong, 
    implementing DoIt() { ..hit-horse..} 
have class Bicycle inherit from MoveAlong, 
    implementing DoIt() { ..pedal..} 
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt 
Isn't this what Strategy intents? (just simpler?) 

[Edit-update] Funkcja odsyłam do powyżej jest zastąpiony przez inną klasę, w której MoveAlong będzie atrybutem, który jest ustawiony zgodnie z potrzebą na podstawie algorytmu zaimplementowanego w tej nowej klasie. (Podobny do tego, co zostanie wykazane w przyjętym odpowiedź.)


[Edit-update] Wnioski

Strategia Wzór ma to zastosowania, ale jestem mocno wierzy w KISS, i na ogół do bardziej prostych i mniej obscuracyjnych technik. Głównie dlatego, że chcę przekazać łatwy do utrzymania kod (i "bo najprawdopodobniej będę tym, który musi wprowadzić zmiany!).

+0

„Is not to, co ma zamiar strategia? (po prostu prostsze?) "<- prawie, z wyjątkiem tego, że podajesz odwołanie do swojej klasy i pozwalasz drugiej klasie wywoływać ją wtedy, gdy jest ona potrzebna, podczas gdy druga klasa wie tylko, że ma ruch typu MoveAlong. To jedna z tych rzeczy, która jest bardziej przydatna w sytuacji zespołowej. – Gerald

+0

Witam gerald Myślę, że twój przykład jest złym przykładem wzoru strategii. tak, jest to strategia, ale w twoim przypadku lepiej byłoby mieć podklasy niż implementować wzorzec strategii. Jestem pewien, że zegar marsjański będzie miał inne zachowanie niż zegar naziemny, stąd jego wartość podklasuje klasę zegara. – RWendi

+0

lol ... Właśnie zobaczyłem ten komentarz. Przypuszczam, że jeśli spodziewasz się mieć klientów na Marsie, to może warto martwić się o zegar marsjański w twoich projektach.Ale ponieważ 100% klientów większości ludzi będzie ludźmi na Ziemi, z uniwersalną reprezentacją danych czasowych, myślę, że całkiem bezpiecznie jest zignorować ten przypadek krawędzi, chyba że jest to gra wideo. – Gerald

Odpowiedz

19

Chodzi o to, aby oddzielić algorytmów do klas, które mogą być podłączone przy starcie. Na przykład załóżmy, że masz aplikację, która zawiera zegar. Istnieje wiele różnych sposobów, dzięki którym można narysować zegar, ale większość funkcji jest taka sama.Więc można utworzyć interfejs wyświetlania zegara:

class IClockDisplay 
{ 
    public: 
     virtual void Display(int hour, int minute, int second) = 0; 
}; 

Wtedy masz klasę zegar, który jest podłączony do zegara i aktualizuje wyświetlanie zegara raz na sekundę. Więc masz coś takiego:

class Clock 
{ 
    protected: 
     IClockDisplay* mDisplay; 
     int mHour; 
     int mMinute; 
     int mSecond; 

    public: 
     Clock(IClockDisplay* display) 
     { 
      mDisplay = display; 
     } 

     void Start(); // initiate the timer 

     void OnTimer() 
     { 
     mDisplay->Display(mHour, mMinute, mSecond); 
     } 

     void ChangeDisplay(IClockDisplay* display) 
     { 
      mDisplay = display; 
     } 
}; 

Następnie w czasie wykonywania tworzysz swój zegar z odpowiednią klasą wyświetlania. np. możesz mieć ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian wszystkie implementujące interfejs IClockDisplay.

Dzięki temu można później dodać dowolny nowy wyświetlacz zegara, tworząc nową klasę bez konieczności bałaganowania w klasie Zegar, bez konieczności nadpisywania metod, które mogą być kłopotliwe w utrzymaniu i debugowaniu.

+0

Więc w moim kodzie pomysłu w pytaniu, funkcja, która przyjmuje MoveAlong jako parm, jest zastępowana odpowiednikiem twojej klasy Clock? – slashmais

+0

Po prostu uderzyło mnie, że jest to podobne do Wzorca dowodzenia, w którym przechodzisz w działaniu na konstruktorze Command. –

+0

przekazywanie obiektu akcji do polecenia * to * przy użyciu wzorca strategii. –

1

W przykładzie na Wikipedii wystąpienia te można przekazać do funkcji, która nie musi obchodzić, do której klasy należą te wystąpienia. Funkcja po prostu wywołuje execute na obiekcie, który minął, i wie, że właściwa rzecz się wydarzy.

Typowym przykładem wzorca strategii jest sposób działania plików w systemie Unix. Biorąc pod uwagę deskryptor pliku, możesz go odczytać, napisać do niego, odpytać, poszukać, wysłać do niego ioctl, bez konieczności sprawdzania, czy masz do czynienia z plikiem, katalogiem, rurą, gniazdem, urządzenie itp. (Oczywiście niektóre operacje, takie jak seek, nie działają na rurach i gniazdach, ale odczyty i zapisy będą działały dobrze w tych przypadkach.)

Oznacza to, że możesz napisać ogólny kod, aby obsłużyć wszystkie te różne typy "plików", bez konieczności pisania oddzielnego kodu do obsługi plików w przeciwieństwie do katalogów itp. Jądro Unix dba o delegowanie wywołań do właściwego kodu.

To jest wzorzec strategii używany w kodzie jądra, ale nie podano, że musiał to być kod użytkownika, tylko przykład z prawdziwego świata. :-)

+0

"przekazany do funkcji, która nie musi obchodzić, do której klasy należą te instancje" - wygląda na to, że dodałeś więcej drzew ;-). Czym się różnią od klas wirtualnych funkcji/polimorfizmów/metod ogólnych? – slashmais

+1

To _jest_ funkcjami wirtualnymi. Wzorzec strategii, w moim rozumieniu, jest po prostu bardzo specyficznym wykorzystaniem funkcji wirtualnych, a mianowicie za pomocą interfejsów (w sensie Java), aby uzyskać dostęp do funkcjonalności, a nie konkretnych klas. –

10

W Javie użyć szyfrowania strumienia wejściowego do odszyfrowania tak:

String path = ... ; 
InputStream = new CipherInputStream(new FileInputStream(path), ???); 

Ale szyfr strumień nie ma wiedzy o tym, co algorytm szyfrowania masz zamiar używać lub rozmiar bloku, strategii dopełnienie etc ... Nowe algorytmy będą dodawane przez cały czas, więc ich kodowanie nie jest praktyczne. Zamiast mijamy w Cipher strategii obiektu, aby poinformować go, jak wykonać deszyfrowania ...

String path = ... ; 
Cipher strategy = ... ; 
InputStream = new CipherInputStream(new FileInputStream(path), strategy); 

W ogóle użyć deseniu strategię każdej chwili masz jakiś obiekt, który zna co musi zrobić ale nie, aby to zrobić.Innym dobrym przykładem są menedżerowie layoutu w Swing, choć w tym przypadku nie wyszło tak dobrze, aby uzyskać zabawną ilustrację, patrz: Totally GridBag.

NB: Istnieją tutaj dwa wzory, ponieważ zawijanie strumieni w strumieniach jest przykładem Decorator.

+1

właśnie obejrzałem bit "Totally GridBag" - mogę odnieść się do niego. lol – slashmais

2

Ten wzór pozwala na enkapsulować algorytmy w klasach.

Klasa, która używa strategii, klasa klienta, jest oddzielona od implementacji algorytmu. Możesz zmienić implementację algorytmów lub dodać nowy algorytm bez konieczności modyfikowania klienta. Można to również zrobić dynamicznie: klient może wybrać algorytm, który będzie używał.

Na przykład wyobraź sobie aplikację, która musi zapisać obraz w pliku; obraz można zapisać w różnych formatach (PNG, JPG ...). Algorytmy kodowania będą zaimplementowane w różnych klasach dzielących ten sam interfejs. Klasa klienta wybierze jedną w zależności od preferencji użytkownika.

4

Wzorzec strategii pozwala na wykorzystanie polimorfizmu bez rozszerzania głównej klasy. Zasadniczo umieszczasz wszystkie zmienne części w interfejsie strategii i implementacjach oraz głównych delegatach klasy. Jeśli twój główny obiekt wykorzystuje tylko jedną strategię, jest prawie taki sam jak posiadanie abstrakcyjnej (czystej wirtualnej) metody i różnych implementacji w każdej podklasie.

Podejście strategia oferuje kilka korzyści:

  • można zmienić strategię przy starcie - Porównaj to do zmiany typu klasy w czasie wykonywania, który jest o wiele trudniejsze, kompilator specyficzny i niemożliwe do metod bez wirtualnych
  • Jedna główna klasa może korzystać z więcej niż jednej strategii, która pozwala na rekombinowanie ich na wiele sposobów. Rozważ klasę, która chodzi po drzewie i ocenia funkcję opartą na każdym węźle i bieżący wynik. Możesz mieć strategię chodzenia (pierwszy lub pierwszy od drugiego) i strategię obliczeniową (jakiś funktor - np. "Zlicz liczb dodatnich" lub "sumę"). Jeśli nie używasz strategii, musisz zaimplementować podklasę dla każdej kombinacji chodzenia/obliczania. Kod
  • jest łatwiejszy w utrzymaniu jak modyfikowanie lub zrozumienia strategii nie wymaga, aby zrozumieć cały Główny przedmiot

Wadą jest to, że w wielu przypadkach wzór strategia jest przesadą - operator switch/case jest nie bez powodu. Rozważ rozpoczęcie od prostych instrukcji sterowania przepływem (switch/case lub if), tylko wtedy, gdy jest to konieczne, przejdź do hierarchii klas i jeśli masz więcej niż jeden wymiar zmienności, wyciągnij zeń strategie. Wskaźniki funkcyjne mieszczą się gdzieś pośrodku tego kontinuum.

Zalecana literatura:

5

Istnieje różnica między strategią a decyzją/wyborem. W większości przypadków będziemy obsługiwać decyzje/wybory w naszym kodzie i realizować je przy użyciu konstruktów if()/switch(). Wzór strategii jest przydatny, gdy zachodzi potrzeba oddzielenia logiki/algorytmu od użycia.

Jako przykład pomyśl o mechanizmie odpytywania, w którym różni użytkownicy sprawdzaliby zasoby/aktualizacje. Teraz możemy chcieć, aby niektórzy z zarejestrowanych użytkowników otrzymywali powiadomienia z szybszym czasem realizacji lub z bardziej szczegółowymi informacjami. Zasadniczo stosowana logika zmienia się w zależności od ról użytkownika. Strategia ma sens z punktu widzenia projektu/architektury, przy niższych poziomach szczegółowości należy zawsze kwestionować.

2

Jednym ze sposobów obejrzenia tego jest sytuacja, w której wykonywanych jest wiele różnych czynności, a działania są określane w czasie wykonywania. Jeśli utworzysz hasz lub słownik strategii, możesz pobrać te strategie, które odpowiadają wartościom komend lub parametrom. Po wybraniu podzbioru możesz po prostu powtórzyć listę strategii i wykonać ją kolejno.

Jednym konkretnym przykładem może być obliczenie sumy zamówienia. Twoje parametry lub komendy to cena podstawowa, podatek lokalny, podatek miejski, podatek państwowy, wysyłka za grunt i zniżka kuponowa. Elastyczność wchodzi w grę, gdy zajmujesz się zmiennością zamówień - niektóre stany nie będą miały podatku od sprzedaży, podczas gdy inne zamówienia będą musiały zastosować kupon. Możesz dynamicznie przydzielać kolejność obliczeń. Dopóki uwzględnisz wszystkie swoje obliczenia, możesz uwzględnić wszystkie kombinacje bez ponownej kompilacji.

0

Strategia działa na zasadzie prostego pomysłu, tj. "Składania ulubionych nad dziedziczeniem", aby strategia/algorytm mógł być zmieniany w czasie wykonywania. Aby zilustrować, zróbmy przykład, w którym musimy zaszyfrować różne wiadomości na podstawie ich typu, np. MailMessage, ChatMessage itp

class CEncryptor 
{ 
    virtual void encrypt() = 0; 
    virtual void decrypt() = 0; 
}; 
class CMessage 
{ 
private: 
    shared_ptr<CEncryptor> m_pcEncryptor; 
public: 
    virtual void send() = 0; 

    virtual void receive() = 0; 

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor) 
    { 
     m_pcEncryptor = arg_pcEncryptor; 
    } 

    void performEncryption() 
    { 
     m_pcEncryptor->encrypt(); 
    } 
}; 

Teraz przy starcie można utworzyć wystąpienia różnych komunikatów odziedziczone CMessage (jak CMailMessage: CMessage publicznego) z różnych encryptors (jak CDESEncryptor: CEncryptor publicznej)

CMessage *ptr = new CMailMessage(); 
ptr->setEncryptor(new CDESEncrypto()); 
ptr->performEncryption(); 
Powiązane problemy