Programowanie

Chain Of Responsibility… bo po co Ci switch.

Chain of responsibility to jeden z moich ulubionych wzorców. Poznałem go w obecnej pracy dopiero i smutno mi, że tak późno, ponieważ rozświetlił i mój kod i moją twarz 🙂

Wzorzec ten pozwala unikać switchów i ładnie porządkuje kod. Dodatkowo spełnia w większości wymagania Open-Close principle (o tym za chwilę). Jest bardzo fajny w użyciu w swej podstawowej wersji która wygląda tak:

 

W kodzie abstrakcyjna klasa Link posiada sterowanie przepływem w zależności od spełnionych założeń. Request idzie po łańcuchu dopóki nie znajdzie się ogniwo (link) które jest w stanie go obsłużyć. W obecnej postaci kod nie posiada zabezpieczenia przed brakiem wywołania. Generalnie działanie HandleRequest powinno być takie, że jak nie znajdzie kolejnego ogniwa to rzuca błędem. Żeby dodać nowy link tworzymy nowe klasy więc kod jest otwarty na rozszerzanie, a niewiele trzeba modyfikować więc właściwie jest zamknięty na modyfikację. Nie w pełni ponieważ połączenia pomiędzy linkami wpisujemy w obecnej postaci kodu w funkcji Main().

Podczas zabawy z funkcyjnymi językami zauważyliśmy, że można zrobić to lepiej:

 

Kod funkcyjny jest dużo bardziej ścisły. Troszeczkę go zmieniłem, względem przykładu obiektowego, tak samo jak podejście do sprawdzania warunku wykonania. Jak widać już na takim prosty przykładzie zaoszczędziliśmy sporo linijek kodu. Działanie jest troszkę magiczne i to jedyny minus jaki znajduję. Z drugiej strony użycie jest naprawdę fajne. Funkcja ConnectLink zwraca nową funkcję która łączy wykonanie 2 funkcji. Przy czym kolejne użycie łączy poprzednio utworzoną Funkcję Connect link z kolejną funkcją. W ten sposób powstaje łańcuch który jest zapisany jako property Chain. Użycie Chain i dostarczenie mu requesta powoduje wywołanie całego łańcucha funkcji i w ten sposób wykonanie patternu Chain of responsibility.

Dzięki takiemu zabiegowi:

  • możemy mniej pisać,
  • łatwiej rozwijać łańcuch który jest w prywatnym polu,
  • o wiele więcej kodu trafiło do prywatnych pól i funkcji.
  • jest mniej miejsc w którym może wystąpić błąd.

Przez taki zabieg:

  • bez tłumaczenia raczej nikt tego w biegu nie zrozumie i uzna za spaghetti code.

9 thoughts on “Chain Of Responsibility… bo po co Ci switch.”

  1. Pingback: dotnetomaniak.pl
  2. Dzięki za artykuł. Brakuje mi jednak odpowiedzi na pytanie w temacie, jak ten wzorzec może zastąpić switch ? Ja bym w funkcji Main w pętli wstawił 3 if’y i wywołanie metody dla warunku. Nie widzę powodu wstawiać switch’a. Może mógłbyś zamieścić przykład switch’a na początku i jak można go zastąpić ?

    1. Te 3 ify niczym nie roznia sie od switcha. Kod tak napisany moze latwo wymknac sie spod kontroli i doprowadzic do powstania 10 ifow. Chain of Responsibility przydaje sie w przypadkach gdzie mialbys duzo takich ifow + chcesz miec kod otwarty na rozszerzenia. Miast pisac nowego ifa i modyfikowac corowy kod dodajesz nowy obiekt do lancucha i voilla.

    2. Każdego switcha możesz przedstawić jako zestaw ifów. Dobą praktyką jest zastępowanie struktury większej niż if.. else switchem właśnie. Przy czym można takiej struktury uniknąć stosując Chain of Responsibility lub w niektórych przypadkach Pattern Matching.
      Zgadzam się w większości z Michałem, przy czym ja mam podejście bardzo “hardcorowe” i staram się mieć kod który nie ma żadnych ifów ani switchów, a zastępować je takimi właśnie rozwiązaniami. A więc nawet w miejscu w którym może być if..else ja postaram się użyć Chain… bo wiem, że ktoś kto dotknie tego kodu zmieniając coś nie zawaha się zrobić if..else if … else ..
      Zrobię specjalnie posta w którym pokażę co spowodowało u nas w programie takie podejście, a efekt refactoringu już widać w poście o Pattern Matching.

  3. Według mnie wzorzec chain of responsibility nie jest do końca dobrym zamiennikiem dla switch ponieważ switch robi skok warunkowy do danego case’a natomiast w łańcuszku trzeba przejść każdy element po kolei aż natrafimy na ten właściwy.

    Lepszym zamiennikiem jak dla mnie dla instrukcji switch jest użycie Dictionary. Zalety to, że możemy wstrzykiwać dowolną implementację do naszej metody, klasy bez modyfikowania jej oraz to, że odnosimy się do konkretnego indeksu a nie tak jak w przypadku łańcuszka, sprawdzamy każdy warunek po kolei. Wady to takie, że każde z tych rozwiązań jest wolniejsze niż zwykły, stary, wysłużony switch.

    1. Nie bardzo rozumiem, jak mogłoby tu pomóc Dictionary (którego używałem do Pattern matchingu – podobny temat też opisany na blogu).

      Tu też nie ma problemu by wstrzyknąć dodatkowe metody/funkcje. Wystarczy co nieco upublicznić i heja! Dictionary jest niebezpieczne, bo działa w nieznanej kolejności (opisałem to w Pattern Matching dla biednych część II).

      Co do wydajności o której mówisz to ma to chyba tylko znaczenie dla producentów gier komputerowych, którzy C# używają do tworzenia narzędzi do gry, a nie samej gry, więc i zapewne dla nich nie ma znaczenia.

        1. Bynajmniej – zawsze chętnie słucham innych rozwiązań, a te które mi się podobają, sam implementuję i od takiego momentu używam.

Leave a Reply

Your email address will not be published. Required fields are marked *