Wzorce projektowe: Visitor

by Piotrosz 22. May 2009
Wzorzec Visitor umożliwia wykonanie takiej samej operacji na każdym obiekcie w złożonej (heterogenicznej) strukturze. Operacja może być specyficzna dla każdego typu elementu tej struktury.

Innymi słowy: Visitor oddziela operację/algorytm od struktury na której działa ta operacja.

We wzorcu uczestniczą obiekty implementujące interfejs IVisitor, który definiuje grupę metod Visit(), przyjmujące jako parametry obiekty typów które ma odwiedzić. Można tu zastosować przeciążenie metody Visit (np. void Visit(Apple a), void Visit(Orange o), ...) albo oddzielne nazwy metod (void VisitApple(Apple a), void VisitOrange(Orange o), ...) - to nie ma większego znaczenia.

Obiekty wchodzące w skład struktury (ObjectStructure) muszą implementować interfejs, który definiuje jedną metodę: Accept() przyjmującą jeden parametr: obiekt typu IVisitor. Dzięki metodzie Accept() element struktury jest przygotowany na przyjęcie odwiedzającego (Visitora). W metodzie Accept() wywoływana jest metoda Visit() obiektu IVisitor z parametrem this.

Uwagi:
Dokładna postać struktury obiektów może być różna (lista, drzewo, etc.) i nie należy do definicji wzorca.

Zaleta: Można dodawać nowe operacje, bez konieczności modyfikacji klas struktury. Nie trzeba dodawać tej operacji na każdym elemencie złożonej struktury.

Wada: trudno jest rozbudowywać pierwotną strukturę (bo wtedy w każdym Visitorze trzeba dodać kod specyficzny dla tego nowego elementu). Dlatego najlepiej, gdy jest ona niezmienna.

Przykład (ściągnąłem pomysł pszczoły odwiedzającej kwiaty z książki Thinking in C++ - mam nadzieję, że nikt się nie obrazi). W idealnym przykładzie pszczoła powinna wykonywać jeszcze jakąś operację na kwiatku, gdy go odwiedza, ale to można sobie łatwo wyobrazić.
using System;
using System.Collections.Generic;

namespace VisitorExample
{
    class MainClass
    {
        public static void Main()
        {
            RandomFlowerGarden garden = new RandomFlowerGarden(5);
            Bee bee = new Bee();
            garden.Accept(bee);
        }
    }

    interface IVisitor
    {
        void Visit(Sunflower s);
        void Visit(Rose r);
        void Visit(Tulip t);
    }

    class Bee : IVisitor
    {
        public void Visit(Rose r)
        {
            // tutaj pszczoła może wykonać jakieś operacje na róży
            Console.WriteLine("Pszczoła odwiedza różę");
        }

        public void Visit(Sunflower s)
        {
            Console.WriteLine("Pszczoła odwiedza słonecznik");
        }

        public void Visit(Tulip t)
        {
            Console.WriteLine("Pszczoła odwiedza tulipana");
        }
    }

    // Drugim odwiedzającym może być np. ważka
    //class Dragonfly : IVisitor
    // TODO

    // Interfejs, który sprawia, że klasa jest "visitor-ready"
    interface IFlower
    {
        void Accept(IVisitor visitor);
    }

    // Róża
    class Rose : IFlower
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }    

    // Tulipan
    class Tulip : IFlower
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // Słonecznik
    class Sunflower : IFlower
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // To jest struktura obiektów - tutaj tablica z różnymi kwiatami
    class RandomFlowerGarden
    {
        private IFlower [] _Flowers ;

        public RandomFlowerGarden(int Count)
        {
            _Flowers = new IFlower[Count];
            Random r = new Random(666);
            for (int i = 0; i < Count; i++)
            {
                switch (r.Next(3))
                {
                    case 0 :
                        _Flowers[i] = new Tulip();
                        break;
                    case 1 :
                        _Flowers[i] = new Rose();
                        break;
                    case 2 :
                        _Flowers[i] = new Sunflower();
                        break;
                }
            }
        }

        public void Accept(IVisitor visitor)
        {
            foreach (IFlower f in _Flowers)
                f.Accept(visitor);
        }
    }
}
Wszędzie podkreśla się, że wzorzec ten jest rzadko używany, a motywacja jego zastosowania jest bardzo pokrętna.
Daje on bardzo mało korzyści i trzeba się 2 razy zastanowić zanim się go zastosuje.

Wzorce projektowe: Composite

by Piotrosz 22. May 2009
Kompozyt (Composite) pozwala na takie zgrupowanie różnych obiektów, że mogą być one traktowane przez klienta w jednakowy sposób.

Przykładem może być wyliczanie zawartości katalogu na dysku. Katalog może zawierać różne elementy: pliki, inne katalogi, drukarki, skróty, etc, ale często chcemy traktować je w ten sam sposób, na przykład by wyświetlić ich nazwę. Ważne jest też to, że w jednakowy spobób chcemy traktować elementy, które nie mają dzieci (Leaves) i te które same są kolekcjami.

W przypadku zastosowania wzorca Composite każdy taki element jest instancją klasy, która implementuje określony interfejs. Interfejs ten zawiera jedną lub więcej operacji do wykonania na elemencie (np. wyświetlenie przyjaznej nazwy, usuwanie, dodawanie, oznaczenie jako ulubione, etc.). Dzięki temu nie trzeba pisać ifów, w których klient rozróżnia typ obiektów, co upraszcza kod. Każdy element można traktować jednakowo (mimo, iż elementy te są różne), co minimalizuje stopień skomplikowania programu.

Przykład. Zamiast interfejsu użyta jest klasa abstrakcyjna Component z dwiema abstrakcyjnymi metodami Display() i Find(). Komponent, który może mieć dzieci ma jeszcze dwie dodatkowe metody: AddChild() i RemoveChild().
using System;
using System.Collections.Generic;

namespace CompositeExample
{
    class Program
    {
        static void Main(string[] args)
        {
            CompositeElement root = new CompositeElement("Root folder");

            root.AddChild(new LeafElement1("File 1"));
            root.AddChild(new LeafElement1("File 2"));
            root.AddChild(new LeafElement1("File 3"));

            CompositeElement comp = new CompositeElement("Subfolder");
            comp.AddChild(new LeafElement1("File 11"));
            comp.AddChild(new LeafElement1("File 22"));

            CompositeElement comp2 = new CompositeElement("SubSubfolder");
            comp2.AddChild(new LeafElement1("File 111"));
            comp.AddChild(comp2);

            root.AddChild(comp);

            root.Display(1); // rekurencyjne wyswietlenie wszystkich elementow

            Console.WriteLine("\nSzukam elementu.\n");
            Component c = root.Find("File 1");
            if (c != null)
                c.Display(1);
            else
                Console.WriteLine("Nie znaleziono takiego komponentu");      
        }
    }

    abstract class Component
    {
        protected string _Name;
        public Component(string Name)
        {
            this._Name = Name;
        }
        // Operacja 1
        public abstract void Display(int Indent);
        // Operacja 2
        public abstract Component Find(string Name);
    }

    class LeafElement1 : Component
    {
        public LeafElement1(string name) : base(name) { }

        public override void Display(int Depth)
        {
            Console.WriteLine("{0}-> {1} [{2}]", new String(' ', Depth), _Name, this.GetType().Name);
        }

        public override Component Find(string Name)
        {
            return (this._Name == Name) ? this : null;
        }
    }

    class CompositeElement : Component
    {
        protected List<Component> _Children = new List<Component>();

        public CompositeElement(string Name) : base(Name) { }
        
        // Operacja 1
        public override void Display(int Depth)
        {
            Console.WriteLine("{0}- {1} [{2}]", new String(' ', Depth), _Name, this.GetType().Name);
            foreach (Component d in _Children)
                d.Display(Depth + 2);
        }

        // Operacja 2
        public override Component Find(string Name)
        {
            Component found = null;
            if (this._Name == Name) return this;
            else
            {
                foreach (Component c in this._Children)
                {
                    found = c.Find(Name);
                    if (found != null) break;
                }
                 
            }
            return found;
        }

        public void AddChild(Component d)
        {
            _Children.Add(d);
        }

        public void RemoveChild(Component d)
        {
            _Children.Remove(d);
        }
    }
}

Wzorce projektowe: Łańcuch odpowiedzialności

by Piotrosz 8. May 2009
Krótki opis wzorca Łańcuch odpowiedzialności (Chain of responsibility).
Wzorzec Chain of Responsibility pracuje z listą obiektów (Handlerów), które implementują interfes IHandler. Handlery te uporządkowane są w łańcuch. Każdy Handler przyjmuje komendę (może to być implementacja wzorca projektowego Command) i jeśli potrafi to zrobić - obsługuje ją. Jeśli dany Handler nie może obsłużyć komendy, przekazuje ją następnemu elementowi w łańcuchu. Na końcu łańcucha może być jakieś zachowanie domyślne lub może być rzucony wyjątek.

Przykładem pozwalającym skojarzyć sobie ten wzorzec z sytuacją z życia codziennego jest petent przychodzący do jakiegoś urzędu. W zależności od ważności sprawy którą ma do załatwienia, zajmuje się nim zwykły urzędnik lub wyższy urzędnik lub szef lub szef wszystkich szefów. Kolejni pracownicy urzędu uporządkowani w hierarchii tworzą łańcuch odpowiedzialności, który ma za zadanie obsłużyć żądanie petenta.
Przykład:
using System;
using System.Collections.Generic;

namespace ChainOfResponsibilityExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Context c = new Context();
            Request r1 = new Request(0, Difficulty.Easy, "Proste żądanie.");
            Request r2 = new Request(1, Difficulty.Difficult, "Trudne żądanie.");
            Request r3 = new Request(2, Difficulty.VeryDifficult, "Bardzo trudne żądanie.");

            c.HandleRequest(r1);
            Console.WriteLine();
            c.HandleRequest(r2);
            Console.WriteLine();
            c.HandleRequest(r3);
        }
    }

    enum CanHandle { Yes, No };
    enum Difficulty { Easy, Difficult, VeryDifficult };

    // Handler abstrakcjny
    interface IHandler
    {
        CanHandle HandleRequest(Request request);
    }

    // Żądanie
    class Request
    {
        public int Id { get; set; }
        public Difficulty DifficultyLevel { get; set; }
        public string Description { get; set; }

        public override string ToString()
        {
            return string.Format("id={0} trudność={1} opis={2}", Id, DifficultyLevel, Description);
        }

        public Request(int id, Difficulty difficulty, string description)
        {
            this.Id = id;
            this.DifficultyLevel = difficulty;
            this.Description = description;
        }
    }

    // Zwykły urzędnik - obsługuje mało proste ządania
    class Clerk : IHandler
    {
        public CanHandle HandleRequest(Request request)
        {
            // zwykły urzędnik obsługuje tylko mało pilne sprawy
            if (request.DifficultyLevel == Difficulty.Easy)
            {
                Console.WriteLine("Szeregowy urzędnik: Obsługuję żądanie: {0}", request);
                return CanHandle.Yes;
            }
            else
            {
                Console.WriteLine("Szeregowy urzędnik: Ta sprawa jest dla mnie za poważna.");
                return CanHandle.No;
            }
        }
    }

    // Wysoki urzędnik - obsługuje trudniejsze żądania
    class SeniorClerk : IHandler
    {
        public CanHandle HandleRequest(Request request)
        {
            if (request.DifficultyLevel == Difficulty.Difficult)
            {
                Console.WriteLine("Wysoki urzędnik: Obsługuję żądanie: {0}", request);
                return CanHandle.Yes;
            }
            else
            {
                Console.WriteLine("Wysoki urzędnik: Nawet ja nie potrafię obsłużyć tak trudnego żądania!");
                return CanHandle.No;
            }
        }
    }

    class Context : IHandler
    {
        private List<IHandler> _Clerks;

        public Context()
        {
            this._Clerks = new List<IHandler>();
            this._Clerks.Add(new Clerk());
            this._Clerks.Add(new SeniorClerk());
            // można dodać kolejne elementy do łańcucha (ale trzeba je sobie dopisać)
        }
        
        public CanHandle HandleRequest(Request request)
        {
            foreach (IHandler handler in this._Clerks)
            {
                if (handler.HandleRequest(request) == CanHandle.Yes)
                {
                    return CanHandle.Yes; // Udało się obsłużyć żądanie
                }
            }
            return CanHandle.No; // Nie udało się obsłużyć żądania. Tu można też rzucić wyjątek.
        }
    }
}
Wynik działania programu:


Zaleta tego wzorca: nie tworzy powiązania między obiektem wysyłającym komendę/żądanie, a obiektem odbierającym tę komendę.

Walidacja numeru NIP

by Piotrosz 6. May 2009
W życiu czasami jest tak, że zachodzi potrzeba zwalidowania polskiego numeru NIP po stronie serwera poprzez sprawdzenie formatu i sumy kontrolnej. Oto krótki przykład z tym związany, może się komuś przyda:
using System;
using System.Text.RegularExpressions;

namespace NipValidator
{
    class Program
    {
        static void Main(string[] args)
        {
            NipValidator v = new NipValidator();
            Console.WriteLine(v.IsValid("1111111111"));
            Console.WriteLine(v.IsValid("2222222222"));
            Console.WriteLine(v.IsValid("1060000062"));

            Console.WriteLine(v.IsValid("assds"));
            Console.WriteLine(v.IsValid("992388"));
            Console.WriteLine(v.IsValid("9892343232"));
        }

        class NipValidator
        {
            private readonly int[] _ValidNums = { 6, 5, 7, 2, 3, 4, 5, 6, 7 };
            private int _Sum;

            public bool IsValid(string Nip)
            {
                if (!Regex.IsMatch(Nip, @"^[\d]{10}$"))
                    return false;
                _Sum = 0;
                for (int t = 8; t >= 0; t--)
                    _Sum += (_ValidNums[t]) * Convert.ToInt32(Nip.Substring(t, 1));
                return ((_Sum % 11) == 10 ? false : ((_Sum % 11) == Convert.ToInt32(Nip.Substring(9, 1))));
            }
        }
    }
}

Tags: ,

C#

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen