poniedziałek, 9 lipca 2012

[Wzorce projektowe] Decorator

Analogia z życia:

Pakiet Matlab - Simulink jest jednym z najpopularniejszych narzędzi do modelowania matematycznego w naukach inżynierskich. Klient może zamówić najtańszą podstawową wersję składającą się z Matlaba oraz podstawowych rozszerzeń Simulinka. Po jakimś czasie może pojawić się potrzeba modelowania ekonomicznego, więc klient jest zmuszony dokupić Financial Toolbox. Zatem podstawowe funkcjonalności zostaną nienaruszone, natomiast całość zostanie rozszerzona o dodatkowe możliwości. Podobnie sytuacja będzie się miała w przypadku przetwarzania obrazów, czy symulacji bioinformatycznych. Zawsze będziemy otrzymywali pewne rozszerzenie posiadanej już wersji.




Zastosowanie:

Wzorzec dekoratora umożliwia dynamiczne rozszerzanie istniejących obiektów. Umożliwia to elastyczność projektowania i staje się poważną konkurencją dla pod klas w hierarchii dziedziczenia. Czasami bowiem mamy do czynienia z zagadnieniem na tyle złożonym, że rozpatrywanie każdego podprzypadku, jako osobnej klasy dziedziczącej z klasy bazowej spowodowałoby rozrośnięcie się programu do ogromnych rozmiarów. Tu z pomocą przychodzi dekorator zapewniający dynamikę przy rozszerzaniu istniejących obiektów. Wzorzec ten może okazać się także przydatny przy rozszerzaniu klas zapieczętowanych (sealed) lub gdy mamy za zadanie rozszerzyć czyjś system, a nie chcemy go modyfikować wewnątrz.

Zasada działania:

Tworzymy pewną bazową abstrakcyjną klasę, lub interfejs, który ma być dekorowany. Konkretne implementacje w konstruktorze przyjmują jako parametr obiekt tej właśnie klasy. W metodach z interfejsu wywoływane są metody z klasy bazowej, plus to, co ma do zaoferowania dekorator. Parametr konstruktora jest zawsze dekorowany przez tworzony tym konstruktorem obiekt.

Przykład implementacyjny:

 public interface IDeveloperFunction
    {
        void Print();
    }

    public class Tester : IDeveloperFunction
    {
        public void Print()
        {
            Console.WriteLine("Tester");
        }
    }

    public class Coder : IDeveloperFunction
    {
        private IDeveloperFunction _myFunction;

        public Coder(IDeveloperFunction function)
        {
            _myFunction = function;
        }

        public void Print()
        {
            if(_myFunction != null)
                _myFunction.Print();
            Console.WriteLine("Coder");
            
        }
    }

    public class Architect : IDeveloperFunction
    {
        private IDeveloperFunction _myFunction;

        public Architect(IDeveloperFunction function)
        {
            _myFunction = function;
        }

        public void Print()
        {
            if(_myFunction != null)
                _myFunction.Print();
            Console.WriteLine("Architect");
            
        }
    }

    public class Manager : IDeveloperFunction
    {
        private IDeveloperFunction _myFunction;

        public Manager(IDeveloperFunction function)
        {
            _myFunction = function;
        }

        public void Print()
        {
            if (_myFunction != null)
                _myFunction.Print();
            Console.WriteLine("Manager");
        }
    }
 class Program
    {
        static void Main(string[] args)
        {
            IDeveloperFunction manager1 = new Manager(null);
            IDeveloperFunction manager2 = new Manager(new Architect(new Coder(new Tester())));
            Console.WriteLine("Manager 1");
            manager1.Print();
            Console.WriteLine("Manager 2");
            manager2.Print();
            Console.Read();
        }
    }

[Wzorce projektowe] Command

Analogia z życia:

Gdy zepsuje nam się pralka dzwonimy po fachowca. Do jego zadań należy dojechać na miejsce, zlokalizować usterkę i naprawić urządzenie. Może się tak jednak zdarzyć, że w międzyczasie coś nam wypadnie i będziemy zmuszeni przesunąć wizytę. Wtedy również bierzemy telefon i informujemy fachowca o nowym terminie. W przypadku, gdy się rozmyślimy i postanowimy kupić nową pralkę, bierzemy telefon i odwołujemy wizytę.



Zastosowanie:


Zadaniem wzorca polecenia jest reprezentowanie akcji jako osobnego obiektu. Umożliwia to odseparowanie klienta, który tę akcję wywołuje od obiektu zajmującego się wywołaniem akcji wraz z jego zależnościami. Podejście takie umożliwia między innymi kolejkowanie akcji, walidację lub cofanie akcji już wykonanych. Z punktu widzenia klienta jest to wywoływanie pojedynczych metod, natomiast cała logika zakodowana zostaje w obiekcie reprezentującym akcję.

Zasada działania:

Komenda reprezentowana jest przez interfejs wystawiający proste, bezparametrowe metody typu wywołaj, waliduj, cofnij. Metody te są wołane przez klienta, gdy zachodzi taka potrzeba.

Przykład implementacyjny:

public interface ICommand
    {
        void Execute();
    }

    public class ExitCommand : ICommand
    {
        public void Execute()
        {
            Environment.Exit(0);
        }
    }

    public class ClearCommand : ICommand
    {
        public void Execute()
        {
            Console.Clear();
        }
    }

    public class AboutCommand : ICommand
    {
        public void Execute()
        {
            Console.WriteLine("This is sample program");
        }
    }

    public class NoSuchCommand : ICommand
    {
        public void Execute()
        {
            Console.WriteLine("Error - no such command");
        }
    }
  public class SimpleCommandFactory
    {
        private List<ICommand> _commands;

        public SimpleCommandFactory()
        {
            _commands = new List<ICommand>()
                            {
                                new AboutCommand(),
                                new ClearCommand(),
                                new ExitCommand()
                            };
        }

        public ICommand GetCommand(string name)
        {
            var cmd = _commands.FirstOrDefault(s => s.GetType().Name.Contains(name));
            if (cmd != null)
                return cmd;
            return new NoSuchCommand();
        }

        public void PrintAvailableCommands()
        {
            foreach (ICommand command in _commands)
            {
                Console.WriteLine(command.GetType().Name);
            }
        }
    }
 class Program
    {
        static void Main(string[] args)
        {
            var factory = new SimpleCommandFactory();
            Console.WriteLine("Available Commands Are :");
            factory.PrintAvailableCommands();
            while (true)
            {
                var arg = Console.ReadLine();
                var cmd = factory.GetCommand(arg);
                cmd.Execute();
            }
        }
    }

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();

        }
    }

[Wzorce projektowe] Builder

Analogia z życia:

Przyrządzanie kawy składa się z kilku etapów : zaparzania, słodzenia i dodatków. Każdy z nich można przeprowadzić na różny sposób. Dodatkami mogą być mleko, śmietanka czy cynamon. Zamawiając kawę moglibyśmy za każdym razem podawać sposób parzenia i dodatki. Przeważnie jednak w menu mamy dostępne gotowe pozycje z opisanymi różnicami, które możemy zamówić.



Zastosowanie:

Wzorca konstruktora używamy przy tworzeniu złożonych obiektów. Skorzystanie z niego umożliwi oddzielenie logiki tworzenia obiektu, na którą składa się wiele etapów od konkretnych implementacji, gdzie za etapami idą konkretne dane. Zapewnia to reużywalnosć - ta sama logika jest w stanie stworzyć wiele obiektów. Warto pamiętać, że te obiekty różnią się danymi, a nie typami.

Zasada działania:

Stosuje się pewną hierarchię przy użyciu wzorca konstruktora. Klasa reżysera, z której API korzysta "reszta świata" ma za zadanie nadzorowanie całością. Przechowuje ona referencję do konkretnego buildera, wywołuje jego metody w odpowiedniej kolejności tak, aby stworzyć obiekt, a także udostępnia metodę, która zwraca instancję obiektu. Klasa abstrakcyjna buildera przechowuje referencję do produktu, udostępnia abstrakcyjne metody, które odpowiadają za etapy tworzenia produktu, a także metodę zwracającą instancję. Konkretne implementacje buildera nadpisują metody abstrakcyjne z klasy bazowej, gdzie zawarta jest konkretna logika przy tworzeniu produktu.


Przykład implementacyjny:

 public class Pizza
    {
        public string Name { get; set; }
        public int DoughHeight { get; set; }
        public List<MeatType> MeatTypes { get; set; }
        public List<CheeseType> CheeseTypes { get; set; }
        public List<string> Vegetables { get; set; }
    }

    public enum MeatType
    {
        Salami,
        Bacon,
        Ham
    }

    public enum CheeseType
    {
        Parmesan,
        Edam,
        Gouda
    }
public abstract class PizzaBuilder
    {
        protected Pizza pizza;

        public Pizza GetPizza()
        {
            return pizza;
        }

        public void CreateNewPizza()
        {
            pizza = new Pizza();
        }

        public abstract void SetName();
        public abstract void MakeDough();
        public abstract void PrepareCheese();
        public abstract void PrepareMeat();
        public abstract void PrepareVegetables();
    }
public class NapoletanaPizzaBuilder : PizzaBuilder
    {
        public override void SetName()
        {
            pizza.Name = "Napoletana";
        }

        public override void MakeDough()
        {
            pizza.DoughHeight = 10;
        }

        public override void PrepareCheese()
        {
            pizza.CheeseTypes = new List<CheeseType>(){CheeseType.Gouda};
        }

        public override void PrepareMeat()
        {
            pizza.MeatTypes = new List<MeatType>() { MeatType.Salami };
        }

        public override void PrepareVegetables()
        {
            pizza.Vegetables = new List<string>()
                                   {
                                       "Olives",
                                       "Paprika"
                                   };
        }
    }
public class SpararePizzaBuilder : PizzaBuilder
    {
        public override void SetName()
        {
            pizza.Name = "Sparare";
        }

        public override void MakeDough()
        {
            pizza.DoughHeight = 8;
        }

        public override void PrepareCheese()
        {
            pizza.CheeseTypes = new List<CheeseType>(){CheeseType.Edam};
        }

        public override void PrepareMeat()
        {
            pizza.MeatTypes = new List<MeatType>(){MeatType.Bacon};
        }

        public override void PrepareVegetables()
        {
            pizza.Vegetables = new List<string>()
                                   {
                                       "Mushrooms", "Onion", "Tomato", "Garlic"
                                   };
        }
    }
public class PizzaDirector
    {
        private PizzaBuilder _builder;

        public PizzaDirector(PizzaBuilder builder)
        {
            _builder = builder;
        }

        public void CreatePizza()
        {
            _builder.CreateNewPizza();
            _builder.SetName();
            _builder.MakeDough();
            _builder.PrepareCheese();
            _builder.PrepareMeat();
            _builder.PrepareVegetables();
        }

        public Pizza GetPizza()
        {
            return _builder.GetPizza();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            var maker = new PizzaDirector(new NapoletanaPizzaBuilder());
            maker.CreatePizza();
            Console.WriteLine(maker.GetPizza().Name);
            maker = new PizzaDirector(new SpararePizzaBuilder());
            maker.CreatePizza();
            Console.WriteLine(maker.GetPizza().Name);
        }
    }

sobota, 7 lipca 2012

[Wzorce projektowe] Template Method

Analogia z życia:

Piłkarze czołowych klubów europejskich trenują według ściśle zaplanowanego grafiku. Jednego dnia trenują wszyscy stałe fragmenty gry, innego grę piłką. W ciągu miesiąca oprócz meczów swojego zespołu, zdarzają się też takie dni, w których wszyscy wyjeżdżają na zgrupowania swoich reprezentacji narodowych. W tym czasie każdy piłkarz zajmuje się czym innym, zgodnie z zaleceniami swojego trenera - selekcjonera. Po powrocie z reprezentacji, piłkarze wracają do wspólnych treningów w klubie.



Zastosowanie:

Wzorca metody szablonu używa się podczas modelowania pewnego algorytmu składającego się z etapów. Każdy etap może być inny, natomiast całość powinna wykonywać się w odpowiedniej kolejności. Zatem zgodnie z założeniem narzucamy jedynie strukturę pewnego algorytmów, natomiast wykonanie kolejnych kroków zależy od konkretnych implementacji. Część kroków, wspólna dla całego algorytmu, może być także zdefiniowana w klasie bazowej.

Zasada działania:

Szablon metody definiowany jest w abstrakcyjnej klasie bazowej, gdzie w odpowiednich miejscach wywoływane są metody abstrakcyjne. Klasy dziedziczące z klasy bazowej redefiniują odpowiednie metody, wprowadzając w nich swoje zachowania. Przydatne mogą się też okazać metody wirtualne.

Przykład implementacyjny:

public abstract class AbstractLetter
    {
        private readonly string _letterContent;

        public AbstractLetter(string letter)
        {
            _letterContent = letter;
        }

        public void PrintLetter()
        {
            Console.Write("\t\t");
            Console.Write(Recipent() + "\n");
            Console.WriteLine();
            Console.WriteLine(_letterContent);
            Console.WriteLine("\t\t" + Signature());
        }

        public abstract string Recipent();
        public abstract string Signature();

    }
    class FormalLetter : AbstractLetter
    {
        public FormalLetter(string s) : base(s)
        {
            
        }

        public override string Recipent()
        {
            return "Dear Sir/Madam";
        }

        public override string Signature()
        {
            return "Yours faithfully";
        }
    }
    public class InformalLetter : AbstractLetter
    {
        public InformalLetter(string s) : base(s)
        {
            
        }
        
        public override string Recipent()
        {
            return "Hi Jack";
        }

        public override string Signature()
        {
            return "See Ya";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string s = "This is sample letter";
            FormalLetter formalLetter = new FormalLetter(s);
            InformalLetter informalLetter = new InformalLetter(s);

            formalLetter.PrintLetter();
            Console.WriteLine();
            informalLetter.PrintLetter();
            Console.WriteLine();
        }
    }

[Wzorce projektowe] Strategy

Analogia z życia:

Podróżując na wakacje można zdecydować się na różne środki transportu. Ktoś woli jechać samochodem, ktoś pociągiem a ktoś inny lecieć samolotem. W każdym z tych przypadków zapłacimy za bilet inną kwotę. W przypadku samolotu płacimy za konkretne połączenie, natomiast w przypadku samochodu zapłacimy za benzynę, czyli koszt będzie zależny od ilości przejechanych kilometrów. Tak więc pomimo tego samego efektu (dotarcie do pewnego miejsca), koszty podróży wyliczone zostaną innym algorytmem.




Zastosowanie: 

Strategii używamy wszędzie tam, gdzie mamy do czynienia z pewnymi rodzinami podobnych algorytmów. Algorytmy te, dzięki wzorcowi mogą rozwijać się niezależnie od klas, które je wykorzystują. Dzięki temu uzyskujemy separację pomiędzy samym działaniem algorytmu, a przetwarzaniem wyników, jakie on zwróci. W każdym miejscu, gdzie pojawiają się wyrażenie switch, if - else i sterują one wywołaniem jakiejś metody, można zastanowić się nad zastosowaniem wzorca strategii. Dzięki temu dodanie nowego algorytmu nie spowoduje modyfikacji klasy, która z niego korzysta.

Zasada działania:

Najprościej użyć wspólnego interfejsu dla rodziny strategii, oraz konkretnych implementacji jako konkretnych algorytmów. 


Przykład implementacyjny:

    public interface ICentralElement
    {
        List<int> Collection { get; set; }
        int CalculateCentralElement();
    }
    public class AverageAlgorithm : ICentralElement
    {
        public List<int> Collection { get; set; }

        public int CalculateCentralElement()
        {
            return (int)Collection.Average();
        }
    }
    public class MedianAlgorithm : ICentralElement
    {
        public List<int> Collection { get; set; }

        public int CalculateCentralElement()
        {
            return Collection[Collection.OrderBy(s => s).ToArray().Count()/2];
        }
    }
 public class CentralElementStrategy
    {
        private readonly ICentralElement _strategy;

        public CentralElementStrategy(ICentralElement strategy)
        {
            _strategy = strategy;

        }

        public int Calculate()
        {
            return _strategy.CalculateCentralElement();
        }
    }
class Program
    {
        public static CentralElementStrategy Strategy;

        static void Main(string[] args)
        {
            RandomStrategy();

            Console.WriteLine(Strategy.Calculate());
        }

        public static void RandomStrategy()
        {
            ICentralElement centralElement;
            Random r = new Random();
            var num = r.Next();
            if (num % 2 == 0)
                centralElement = new MedianAlgorithm();
            else
                centralElement = new AverageAlgorithm();

            List<int> list = new List<int>();
            for (int i = 0; i < 20; i++)
            {
                list.Add(r.Next());
            }
            centralElement.Collection = list;

            Strategy = new CentralElementStrategy(centralElement);
        }
    }

Platforma .NET Framework udostępnia pewne mechanizmy ułatwiające implementację wzorca strategii, takie jak delegaty np. Func<>, Action<>, Predicate<> itd.

piątek, 6 lipca 2012

[Wzorce projektowe] Observer

Analogia z życia:

Grupy google umożliwiają prowadzenie rozległych dyskusji na konkretne tematy. Zakładając grupę dyskusyjną mamy możliwość wysyłania do niej wiadomości. Każdy zainteresowany tematem może sobie zasubskrybować grupę i otrzymywać wszystkie wysyłane na nią wiadomości. Jeśli temat przestanie go interesować, może anulować subskrypcję. Osoba wysyłająca wiadomości nie musi przejmować się tym, kto obecnie subskrybuje jej wiadomości - są one wysyłane na adres grupy.





Zastosowanie:

Wzorzec obserwatora stosujemy, gdy jeden obiekt zależny jest od innego i zmiana stanu jednego obiektu powinna powodować zmianę innych obiektów. Często używając wzorca obserwatora nie mamy wiedzy na temat tego, jakie obiekty powiadamiamy.

Zasada działania:

Tradycyjne podejście do wzorca obserwatora zakłada stworzenie dwóch klas abstrakcyjnych : podmiotu i obserwatora. Obserwator zawiera referencję do podmiotu i może go aktualizować. Podmiot może rejestrować nowych obserwatorów (zawiera kolekcję obserwatorów), a także usuwać już zarejestrowanych. Może także notyfikować wszystkich o zmianie stanu. Ponadto może się też zdarzyć, że obserwatorzy będą chcieli modyfikować źródło notyfikacji, co należy odpowiednio zaimplementować.

Przykład implementacyjny:


public interface IAbstractObserver
    {
        void Update(int number);
    }

    public class Counter : IAbstractObserver
    {
        public Counter(IAbstractSubject sub)
        {
            sub.Subscribe(this);
        }

        public static int Count { get; set; }

        public void Update(int n)
        {
            Count++;
        }
    }

    public class Printer : IAbstractObserver
    {
        public Printer(IAbstractSubject sub)
        {
            sub.Subscribe(this);
        }

        public void Update(int n)
        {
            Console.WriteLine(n);
        }
    }
public interface IAbstractSubject
    {
        void Subscribe(IAbstractObserver observer);
        void Unsubscribe(IAbstractObserver observer);
        void Notify();
    }

    public class RandomNumbers : IAbstractSubject
    {
        private List<IAbstractObserver> _observers = new List<IAbstractObserver>();


        private int _randomNumber;
        public int RandomNumber
        {
            get { return _randomNumber; }
            set
            {
                _randomNumber = value;
                Notify();
            }
        }

        public void Subscribe(IAbstractObserver observer)
        {
            _observers.Add(observer);
        }

        public void Unsubscribe(IAbstractObserver observer)
        {
            _observers.Remove(observer);
        }

        public void Notify()
        {
            foreach (IAbstractObserver abstractObserver in _observers)
            {
                abstractObserver.Update(_randomNumber);
            }
        }
    }
 class Program
    {
        static void Main(string[] args)
        {
            RandomNumbers rn = new RandomNumbers();
            Printer printer = new Printer(rn);
            Counter counter = new Counter(rn);

            Random r = new Random();

            for (int i = 0; i < 10; i++)
            {
                int number = r.Next(0, 10);
                rn.RandomNumber = number;
                Thread.Sleep(number*100);
            }

            Console.WriteLine(Counter.Count);
            Console.Read();
        }
    }

Powyższa implementacja jest podejściem klasycznym. Platforma .NET Framework udostępnia zdarzenia i delegatów, za pomocą których można uzyskać podobny efekt.