Łańcuch zobowiązań (Chain of responsibility)

Posted on by 0 comment

Kolejnym wzorcem, który wpadł w sidła cyklu wpisów o wzorcach projektowych, jest czynnościowy wzorzec projektowy (opisujące zachowanie) o ciekawej nazwie łańcuch zobowiązań, zwany także łańcuchem odpowiedzialności. Jego zasada działania jest stosunkowo prosta, za jego pomocą świetnie zaimplementowalibyśmy przerzucanie obowiązków między ludźmi w grupie, więcej w rozwinięciu artykułu.

Lista jednokierunkowa

Zacznę od przypomnienia czym jest lista kierunkowa.

Lista jednokierunkowa jest to struktura, w której każdy element zna adres następnego, o ile on istnieje. Charakterystycznymi cechami tej listy są szybkie operacje wstawiania oraz usuwania elementów.

 

Lista jednokierunkowa – Łańcuch zobowiązań (Chain of responsibility) wzorzec projektowy

Lista jednokierunkowa – Łańcuch zobowiązań (Chain of responsibility) wzorzec projektowy

Łańcuch zobowiązań

Filarem wzorca jest lista jednokierunkowa oraz klasa abstrakcyjna, która jest szablonem dla składników tej listy. Każda klasa, która dziedziczy po owej klasie abstrakcyjnej musi mieć zaimplementowaną metodę, która być może obsłuży odebrane zadanie. Dlaczego być może? Obiekt, który rozważa możliwość wykonania otrzymanego zadania, musi być na to gotowy, dlatego sprawdza, czy spełnia warunki, które upoważniają go do obsłużenia zadania. Jeśli je spełnia, to zajmuje się pracą, a jeśli nie, to próbuje zrzucić odpowiedzialność, zgodnie z zasadą działania listy kierunkowej na kolejnego, znanego mu przedstawiciela.

Diagram klas

Diagram klas – Łańcuch zobowiązań (Chain of responsibility) wzorzec projektowy

Diagram klas – Łańcuch zobowiązań (Chain of responsibility) wzorzec projektowy

 

Na powyższym diagramie klas, zidentyfikować można trzy grupy obiektów, są to:

  • Klient (client) – deklaruje żądanie wykonania jakiejś akcji, zleca jej wykonanie,
  • AbstrakcyjnaObsługa (handler) – definiuje interfejs dla właściwych wykonawców usługi, dostarcza mechanizm kolejki,
  • ImplementacjaObslugi (concrete handler) – właściwy wykonawca, jeśli spełnia jego warunki obsługuje żądanie. Wie, czy istnieje następny wykonawca i posiada jego adres.

Diagram klas jest wiernym odzwierciedleniem opisu, jakim zdefiniowałem łańcuch zobowiązań.

Kod źródłowy

Szukając zastosowania dla tego wzorca, padło na warsztat samochodowy. Zainsynuujmy jego pracę Postanowiłem, że nasz abstrakcyjny warsztat, stosuje podział czynności na kilka grup. Niech przykładowo każdą grupę zadań obsługuje jeden pracownik, poniżej umieszczam plik Zadania.cs, w którym zdefiniowane są możliwe kategorie czynności do wykonania.

namespace LancuchZobowiazan
{
    enum Zadania
    {
        Audi,
        BMW,
        Finanse,
        Inne
    }
}

Postanowiłem, że rodzaje zadań zdefiniuje w typie wyliczeniowym enum.

W dalszym kroku zajmę się dostarczeniem klasy abstrakcyjnej Pracownik, która implementuje wzorzec łańcuch zobowiązań oraz jest szablonem dla każdego zatrudnionego w tym warsztacie.

namespace LancuchZobowiazan
{
    abstract class Pracownik
    {
        public string Imie { get; set; }

        //wskaźnik na następnego pracownika
        protected Pracownik kolejny;
        
        ///
        /// Metoda ustawiająca adres do nastepengo pracownika
        ///

///Adres do obiektu pracownika public void UstawKolejnego(Pracownik pracownik) { kolejny = pracownik; } ///

        /// Metoda symulująca obsługę zadania
        ///

///Rodzaj zadania public abstract void WykonajZadanie(Zadania zadanie); } }

Jak widzimy znajduje się tutaj właściwość kolejny, która będzie wskazywać na następnego pracownika oraz metoda UstawKolejnego, służąca do definiowania tej właściwości. Można dostrzec także funkcję WykonajZadanie, która będzie obsługiwać żądania klientów, a sama jej obecność tutaj, zmusza klasy dziedziczące do dostarczenia jej ciała.

Poniżej umieszczam kod trzech klas KierownikMechanikAudiMechanikBMW, które dziedziczą po abstrakcyjnym szablonie Pracownik. Warto zwrócić uwagę na ciało wspomnianej chwilę temu metody WykonajZadanie, a szczególnie na instrukcję warunkową, która sprawdza, czy obiekt może obsłużyć zadanie. Jeśli nie, to w razie możliwości (jeśli taki istnieje, czyli referencja do następnego obiektu nie jest NULL) przekazuje ona odpowiedzialność na następny obiekt.

Kierownik.cs

using System;

namespace LancuchZobowiazan
{
    class Kierownik : Pracownik
    {
        public Kierownik(string imie)
        {
            this.Imie = imie;
        }

        public override void WykonajZadanie(Zadania zadanie)
        {
            //sprawdzenie, czy ten obiekt powinien obsłużyć żądanie
            if(zadanie == Zadania.Finanse)
            {
                Console.WriteLine("{0} o imieniu {1} obsługuje zadanie: {2}", 
                    this.GetType().Name, Imie, zadanie);
            }
            else if(zadanie == Zadania.Inne)
            {
                Console.WriteLine("{0} o imieniu {1} obsługuje zadanie: {2}",
                    this.GetType().Name, Imie, zadanie);
            }
            else if(kolejny != null)
            {
                kolejny.WykonajZadanie(zadanie);
            }

        }
    }
}

MechanikAudi.cs

using System;

namespace LancuchZobowiazan
{
    class MechanikAudi : Pracownik
    {
        public MechanikAudi(string imie)
        {
            this.Imie = imie;
        }

        public override void WykonajZadanie(Zadania zadanie)
        {
            //sprawdzenie, czy ten obiekt powinien obsłużyć żądanie
            if(zadanie == Zadania.Audi)
            {
                Console.WriteLine("{0} o imieniu {1} obsługuje zadanie: {2}",
                    this.GetType().Name, Imie, zadanie);
            }
            else if(kolejny != null)
            {
                kolejny.WykonajZadanie(zadanie);
            }
        }
    }
}

 MechanikBMW.cs

using System;

namespace LancuchZobowiazan
{
    class MechanikBMW : Pracownik
    {
        public MechanikBMW(string imie)
        {
            this.Imie = imie;
        }

        public override void WykonajZadanie(Zadania zadanie)
        {
            //sprawdzenie, czy ten obiekt powinien obsłużyć żądanie
            if (zadanie == Zadania.BMW)
            {
                Console.WriteLine("{0} o imieniu {1} obsługuje zadanie: {2}",
                    this.GetType().Name, Imie, zadanie);
            }
            else if (kolejny != null)
            {
                kolejny.WykonajZadanie(zadanie);
            }
        }
    }
}

Zobaczmy teraz, jak to wszystko spisuje się w praktyce. Poniżej plik Program.cs z metodą Main.

using System.Collections.Generic;

namespace LancuchZobowiazan
{
    class Program
    {
        static void Main(string[] args)
        {
            //Zainicjowanie obiektów
            Pracownik kierownikAndrzej = new Kierownik("Andrzej");
            Pracownik mechanikAudi = new MechanikAudi("Tomek");
            Pracownik mechanikBMW = new MechanikBMW("Władek");

            //Ustawienie elementów listy jednokierunkowej
            kierownikAndrzej.UstawKolejnego(mechanikAudi);
            mechanikAudi.UstawKolejnego(mechanikBMW);

            //Przykładowy zbiór zadań
            List zadaniaNaDzis = new List {
                Zadania.BMW,
                Zadania.Finanse,
                Zadania.Audi,
                Zadania.Inne
            };

            //Poszukiwanie odpowiedzialnego za dane zadanie
            foreach (var zadanie in zadaniaNaDzis)
            {
                kierownikAndrzej.WykonajZadanie(zadanie);
            }

        }
    }
}

Wyjście:

MechanikBMW o imieniu Władek obsługuje zadanie: BMW
Kierownik o imieniu Andrzej obsługuje zadanie: Finanse
MechanikAudi o imieniu Tomek obsługuje zadanie: Audi
Kierownik o imieniu Andrzej obsługuje zadanie: Inne

Głównym motorem, który inicjuje zadania jest tutaj pętla foreachZaletą, a zarazem i wadą tego wzorca projektowego łańcuch zobowiązań jest odseparowanie klienta od procesu wykonywania metody. Klient, tylko zleca zadanie i nie wie kto się jego podejmie. Jest to, de facto wada, ponieważ w tym wydaniu, klient nie jest nawet pewien, że ktokolwiek podejmie się obsługi jego żądania, tak samo będzie w przypadku wystąpienia błędów.

Podsumowanie

Patrząc poglądowo łańcuch zobowiązań, to elastyczny i przyjemny w implementacji wzorzec projektowy. Stosowany tam, gdzie żądania można z łatwością kategoryzować, dzieląc je tym samym na zróżnicowane grupy. Często stosuje się mechanizmy, które informują o poprawności wykonania żądania. Ponadto wzorzec, w zależności od potrzeb, po obsłużeniu  żądania, może dalej wędrować po kolejce, szukając kolejnego wykonawcy, lub tak jak w tym wpisie, zakończyć przeszukiwanie kolejki.

Wpis należy do serii postów o wzorcach projektowych.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *