sobota, 8 maja 2010

Command Pattern

Dzisiaj trochę o wzorcu projektowym Command. 
Zgodnie z definicją, wzorzec ten enkapsuluje polecenia (metody z ich parametrami) jako obiekty. Pozwala także na korzystanie z tzw. poleceń odwracalnych (undoalbe operations).


Bardzo dobry przykład został zaprezentowany przez twórców książki Head First: Design Patterns. Zobaczmy na przykładowy rysunek:


Jak widać mamy tu do czynienia z klientem, którzy przychodzi do sprzedawcy pieczywa. Zamawia chleb i bułki. Sprzedawca wysyła zamówienie do piekarni. Tak na prawdę sprzedawcę nie interesuje to, w jaki sposób zostanie wykonany chleb czy bułki - chce tylko otrzymać zamówiony produkt. 
Spójrzmy teraz na przykładowy diagram UML tego wzorca:


Client -  odpowiedzialny jest za utworzenie konkretnego polecenia i jego odbiorcy
Reciver - odbiorca wie w jaki sposób zrealizować polecenie
ConcreteCommand - definiuje połączenie pomiędzy odbiorcą i poleceniem
Command - interfejs dla wszystkich poleceń. Posiada najczęściej 2 metody Execute (wywołującą polecenie) oraz Undo (pozwala na odwrócenie zmian).
Invoker - przechowuje polecenie oraz ustala odbiorcę polecenia

Spróbujmy zaimplementować jakiś przykład, który pozwoli nam zobaczyć jak wygląda zastosowanie tego wzorca:


Mamy tutaj prosty przykład piekarni. Klient przychodzi i zamawia bułki albo chleb. Dołożenie kolejnego produktu nie jest problemem - wystarczy tylko napisać kolejny [Klasa ]OnCommand] i zaimplementować odpowiednie metody. Zobaczmy na przykładowy kod:

    public class Bakery
    {
        private ICommand _command;

        public ICommand ICommand
        {
            set
            {
                _command = value;
            }
        }

        public void StartBaking()
        {
            _command.Execute();
        }
    }

    public interface ICommand
    {
        void Execute();
    }

    public class Bread
    {
        private KindOfBread _kindOfBread;
        public KindOfBread KindOfBread
        {
            get
            {
                return _kindOfBread;
            }
            set
            {
                _kindOfBread = value;
            }
        }

        public Bread(KindOfBread kindOfBread)
        {
            this._kindOfBread = kindOfBread;
        }

        public void Bake()
        {
            Console.WriteLine("I'm Baking {0} bread", this._kindOfBread);
        }
    }

    public enum KindOfBread
    {
        White,
        Black,
    }

    public class BakeBreadCommand : ICommand
    {
        private Bread _bread;
        public BakeBreadCommand(Bread bread)
        {
            this._bread = bread;
        }

        public void Execute()
        {
            _bread.Bake();
        }
    }

    public class Roll
    {
        public void Bake()
        {
            Console.WriteLine("I'm baking a roll");
        }
    }

    public class BakeRollCommand : ICommand
    {
        private Roll _roll;
        public BakeRollCommand(Roll roll)
        {
            this._roll = roll;
        }

        public void Execute()
        {
            _roll.Bake();
        }
    }

        static void Main(string[] args)
        {
            Bakery bakrery = new Bakery();
            Roll roll = new Roll();
            BakeRollCommand brc = new BakeRollCommand(roll);
            bakrery.ICommand = brc;
            bakrery.StartBaking();

            Bread bread = new Bread(KindOfBread.Black);
            BakeBreadCommand bbc = new BakeBreadCommand(bread);
            bakrery.ICommand = bbc;
            bakrery.StartBaking();
        }

Jak widać implementacja jest bardzo prosta i łatwa. Kiedy może przydać się ten wzorzec?
- Prostszy i bardziej przejrzysty kod (stosowanie niekończących się instrukcji if nie jest dobrym stylem programistycznym);
- Łatwe przekazywanie parametrów polecenia (wykorzystanie konstruktora)
- Możliwość cofnięcia dokonanych zmian (np. zastosowanie wzorca Memento)
- Nagrywanie makr - można nagrać sekwencję poleceń, a następnie je wykonać; Przykład:
Chcemy, aby po przyjściu stałego klienta, sprzedawca od razu zamówił dla niego bułkę i chleb. Jak to w prosty sposób zrobić?

    public class OurCustomer : ICommand
    {
        private ICommand[] _comands;

        public OurCustomer(ICommand[] commands)
        {
            this._comands = commands;
        }

        public void Execute()
        {
            foreach (var item in _comands)
            {
                item.Execute();
            }
        }
    }

        static void Main(string[] args)
        {
            Bakery bakrery = new Bakery();
            Roll roll = new Roll();
            BakeRollCommand brc = new BakeRollCommand(roll);

            Bread bread = new Bread(KindOfBread.Black);
            BakeBreadCommand bbc = new BakeBreadCommand(bread);

            OurCustomer oc = new OurCustomer(new ICommand[] { brc, bbc });
            bakrery.ICommand = oc;
            bakrery.StartBaking();
        }

Jak więc widać, zmiana dotyczy stworzenia nowej klasy implementującej ICommand, oraz przechowującą wszstkie przekazane polecenia (jako tablica, lista itp).
Dzięki takiemu rozwiązaniu można tworzyć kolejki zadań obsługiwane przez różne wątki. 

Brak komentarzy:

Prześlij komentarz