Archiwum kategorii: Design Patterns

Łańcuch odpowiedzialności

Książkowa definicja: łańcuch odpowiedzialności (chain of responsibility) – służy do stworzenia łańcucha obiektów, które przetwarzają żądanie. Przetwarzanie żądania odbywa się w każdym obiekcie należącym do łańcucha odpowiedzialności. Obiekt obsługuje żądanie albo wysyła je dalej.

Kiedy:

  • piszemy program który musi wykonać pewne zadania sekwencyjnie, jedno za drugim
  • zachodzi potrzeba zamienienia kolejności wykonywania zadań lub tymczasowego pominięcia wykonywania niektórych z nich

Oczywiście można zrobić wszystko w 1 metodzie, albo w 1 klasie z użyciem kilku metod, ale najbardziej przejrzystym sposobem według mnie jest przypisanie zadania do klasy, dzięki temu stosujemy się do 1 zasady z SOLID – S – pojedynczej odpowiedzialności.

Poniżej przygotowałem wstępną implementację wzorca ‚łańcuch odpowiedzialności’ całkowicie po polsku tylko dla przykładu.

1 krok, stworzenie odpowiednich interfejsów i klasy stanu.

Żadnego „rocket since” – nasze zadanie ma coś wykonywać na podstawie stanu. FabrykaZadan ma nam dostarczać gotowe zadania, co by nie musieć samemu sobie tworzyć instancji obiektów. Stan to prosty kontener na dane dodajemy tam coś co jest obecne w każdym zadaniu. Można np. dodać żądanie, flagę isError, listę komunikatów, czas wykonania, długość wykonania (per zadanie) i etc.

Implementacje dla naszych interfejsów wykonałem tak jak poniżej.

Na 1 rzut oka strasznie/obrzydliwie/okropnie wygląda fabryka zadań, jak na to patrzę to dostaje gęsiej skórki ale zignorujcie to na chwilę – moja fabryka ma 1 cel -> dostarczyć instancję obiektu – i to właśnie robi. Mam na to lepsze rozwiązanie, znajdziesz je w ciekawostka.

Dodałem również nowe zadania. Pierwsze i drugie  są zadaniami które robią „to coś” co do nich należy.

Natomiast ciekawszym zadaniem jest zadanie kompozytowe które umożliwia zbudowanie drzewka zadań. Jego implementacja jest prosta. Dostaje w parametrze listę zadań które muszę po kolei wykonać.

Pozostaje jedynie przetestować działanie naszego łańcucha.

Stworzyłem fabrykę zadań z której pobieram zadanie kompozytowe i dalej wykorzystuje fabrykę do sterowania kolejnością wykonywania zadań. Takie zaprojektowanie aplikacji zapewnia dużą przejrzystość kodu i o ile nie będziemy wykorzystywać stanu jako obiektu do przekazywania danych między zadaniami kolejność wykonywania zadań może być dowolna.

Efektem uruchomienia programu będzie oczywiście:

  • 1
  • 2
  • 2
  • 1

Przykład: Idealnym przykładem z życia do zastosowania wzorca łańcucha odpowiedzialności jest parsowanie stron HTML z jakimiś losowymi danymi. Proces parsowania można podzielić na istotne etapy (w przypadku parsowania oczywiście odpada nam losowa kolejność wykonywania zadań, ale dajmy na to, że wykonywaliśmy krok konwertowania danych z base64, a na parsowanej stronie dane przestały być zamieniane do base’a, jedyne co musimy zrobić to za komentować (być może zaraz się znowu zmieni – warto dodać też komentarz dlaczego) wykorzystanie  zadania odczytującego base’a).

Ciekawostka: Jedna rzecz na koniec odnośnie mojej fabryki zadań. Każdy chyba stwierdził, że jest ona „strasznie nie fajna”. W komercyjnych projektach zamiast tego używam rozszerzenia Ninject.Factory które pozwala mi zarejestrować sam interfejs IFabrykaZadan jako fabrykę. Po wstrzyknięciu przez kontener IFabrykaZadan mamy obiekt który zwraca nam w momencie wywołania metody instancję obiektu dla zadania. Rozwiązanie to pozwala nam na pozbycie się klasy FabrykaZadan która w sumie nic nie robi, a trzeba ją utrzymywać.