niedziela, 8 lipca 2012

[Wzorce projektowe] Chain of Responsibility

Analogia z życia:

Często zdarza się, że w dużej korporacji szeregowy pracownik zgłasza zapotrzebowanie zakupu jakiegoś drogiego urządzenia, np. tokarki CNC za cenę ponad 100 000 zł. Zgłoszenie to przekazuje swojemu kierownikowi. Kierownik rozważa pewne za i przeciw i może takie zgłoszenie odrzucić, lub przekazać do dyrektora działu. Dyrektor działu konsultuje pomysł ze specjalistami i gdy uzna, że takie urządzenia będzie przydatne, zgłasza zapotrzebowanie do prezesa firmy, a ten kontaktuje się z pracownikiem aby omówić szczegóły zakupu. Jednak w przypadku, gdy pracownik zgłosi zapotrzebowanie na coś tańszego, zgłoszenie zostanie zaakceptowane na niższym poziomie, np. zakup monitora może zatwierdzać kierownik, bez wiedzy przełożonych.



Zastosowanie:

Łańcucha odpowiedzialności używa się wszędzie tam, gdzie mamy do czynienia z wiadomością przekazywaną od nadawcy, do hierarchii odbiorców. Zależnie od treści wiadomości, pierwszy odbiorca może ją przekazać do następnego odbiorcy lub samemu przetworzyć i wysłać odpowiedź, nie informując o tym następnych odbiorców. Nadawca jest zawsze świadomy tylko pierwszego odbiorcy, natomiast każdy odbiorca wie tylko o następnym nadawcy. Nadawca nie wie, kto konkretnie przetworzył wiadomość, interesuje go tylko odpowiedź. Warto także pamiętać o tym, że kolejność jest ważna i należy także obsłużyć przypadek, gdy żaden odbiorca nie przetworzy wiadomości. Zastosowanie wzorca umożliwia dynamiczne zarządzanie MessageHandlerami a także ich hierarchizację.

Zasada działania:

W pierwszej kolejności należy utworzyć interfejs reprezentujący wiadomość, którą należy przetworzyć, a także zastanowić się nad tym, w jakiej postaci ma być przekazywana odpowiedź np. enum. Drugi interfejs będą implementowali odbiorcy wiadomości, którzy będą musieli przeładować metodę zwracają odpowiedź (np. enum) a przyjmującą obiekt typu wiadomości. Odbiorca musi także przechowywać referencję do następnego odbiorcy, który jest wywoływany, gdy odpowiedź nie może zostać przetworzona. Należy pamiętać o zaimplementowaniu w jakiś sposób ostatniego ogniwa z łańcucha.

Przykład implementacyjny:


public interface IPurchaseRequest
    {
        decimal Cost { get; set; }
    }

    public class PurchaseRequest : IPurchaseRequest
    {
        public string Name { get; set; }

        public PurchaseRequest(decimal cost)
        {
            Cost = cost;
        }

        public decimal Cost
        {
            get; set; 
        }
    }

    public interface IPurchaseApprover
    {
        Response ConsiderRequest(IPurchaseRequest request);
        void SetNextSupervisor(IPurchaseApprover approver);
    }

    public enum Response
    {
        Granted,
        Denied,
        NotAllowedToDecide
    }
public class Supervisor : IPurchaseApprover
    {
        public decimal ApprovalLimit { get; set; }
        public IPurchaseApprover Next { get; set; }
        public string Name { get; set; }

        public Supervisor(string n, decimal l)
        {
            ApprovalLimit = l;
            Name = n;
            Next = EndOfChain.Instance;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            Next = approver;
        }

        public Response ConsiderRequest(IPurchaseRequest request)
        {
            if (request.Cost < ApprovalLimit)
            {
                return Response.Granted;
            }
            return Next.ConsiderRequest(request);
        }
    }
 public static class EndOfChain
    {
        public static IPurchaseApprover Instance
        {
            get { return new Denier(); }
        }
    }

    public class Denier : IPurchaseApprover
    {
        public Response ConsiderRequest(IPurchaseRequest request)
        {
            return Response.Denied;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            throw new NotImplementedException();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            var cncRequest = new PurchaseRequest(120000);
            cncRequest.Name = "CNC";

            IPurchaseApprover chief = new Supervisor("Chief Tom", 1000);
            IPurchaseApprover cto = new Supervisor("CTO Mark", 50000);
            IPurchaseApprover owner = new Supervisor("Owner Mike", 500000);
            
            chief.SetNextSupervisor(cto);
            cto.SetNextSupervisor(owner);

            Console.WriteLine("We need to buy CNC");
            var resp = chief.ConsiderRequest(cncRequest);
            if(resp == Response.Granted)
                Console.WriteLine("OK, Let's do it");
            else
            {
                Console.WriteLine("We do not need it");
            }
            Console.ReadKey();

        }
    }

Brak komentarzy:

Prześlij komentarz