Pokazywanie postów oznaczonych etykietą subscribe. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą subscribe. Pokaż wszystkie posty

wtorek, 10 lipca 2012

[Wzorce projektowe] Event Aggregator

Analogia z życia: 


Nowe wydania gazet pojawiają się przeważnie każdego dnia, co tydzień lub np. co miesiąc. Kiedy tylko wydawnictwo przygotuje całe wydanie, wysyła je do kiosku. Czytelnik zainteresowany swoimi ulubionymi gazetami udaje się do kiosku by nabyć nowe egzemplarze. Informacje na temat tego, czy nowe wydanie już jest dostępne uzyskuje właśnie od kioskarza.



Zastosowanie:

Event Aggregator upraszcza zarządzanie zdarzeniami przez wprowadzenie jednego scentralizowanego obiektu zarządzającego nimi. Dzięki temu powiązania między nadawcami i odbiorcami eventów są zredukowane do minimum. Zatem wzorca tego należy użyć wszędzie tam, gdzie mamy do czynienia z wieloma modułami, które muszą się ze sobą komunikować, a także w każdym innym przypadku, gdzie relacja między nadawcami a odbiorcami zdarzeń przedstawia się jako wiele do wielu. Dodatkowo zmniejszone zostaje ryzyko wycieków pamięci związanych z eventami, a wprowadzenie nowego zdarzenia nie zaburza struktury całości.

Zasada działania:

W omawianym wzorcu występują trzy rodzaje obiektów : nadawcy, odbiorcy i agregator. Zarówno nadawcy, jak i odbiorcy przechowują referencję do agregatora. Gdy zajdzie taka potrzeba nadawcy wywołują odpowiednie metody z agregatora notyfikując odbiorców. Odbiorcy wołają metody subskrypcji by odbierać wiadomości, które ich interesują. Odbiorca może na przykład implementować generyczny interfejs, mówiący o tym, które eventy chce subskrybować. Event Aggregator przechowuje jedynie słabe referencje (takie, które nie powstrzymają GC przed sprzątnięciem obiektu) do subskrybentów, co zapobiega wyciekom pamięci.

Przykład implementacyjny:

 public interface IEventAggregator
    {
        void Subscribe(object subscriber);
        void Publish<TEvent>(TEvent eventToSend);
    }

    public class SimpleEventAggregator : IEventAggregator
    {
        private readonly Dictionary<Type, List<WeakReference>> _subscribersList = 
            new Dictionary<Type, List<WeakReference>>();

        private readonly object _syncObject = new object();

        public void Subscribe(object subscriber)
        {
            lock (_syncObject)
            {
                var subscriberTypes = subscriber.GetType().GetInterfaces().Where(i => i.IsGenericType
                                                                                      &&
                                                                                      i.GetGenericTypeDefinition() ==
                                                                                      typeof (ISubscriber<>));
                var weakReference = new WeakReference(subscriber);
                foreach (Type subscriberType in subscriberTypes)
                {
                    var subscribers = GetSubscribers(subscriberType);
                    subscribers.Add(weakReference);
                }
            }
        }

        private List<WeakReference> GetSubscribers(Type subscriberType)
        {
            List<WeakReference> subscribers;
            lock (_syncObject)
            {
                var found = _subscribersList.TryGetValue(subscriberType, out subscribers);
                if(!found)
                {
                    subscribers = new List<WeakReference>();
                    _subscribersList.Add(subscriberType, subscribers);
                }
            }
            return subscribers;
        }

        public void Publish<TEvent>(TEvent eventToSend)
        {
            var subscriberType = typeof (ISubscriber<>).MakeGenericType(typeof (TEvent));
            var subscribers = GetSubscribers(subscriberType);
            List<WeakReference> toRemove = new List<WeakReference>();

            foreach (WeakReference weakSubscriber in subscribers)
            {
                if(weakSubscriber.IsAlive)
                {
                    var subscriber = (ISubscriber<TEvent>) weakSubscriber.Target;
                    var syncContext = SynchronizationContext.Current;
                    if(syncContext == null)
                        syncContext = new SynchronizationContext();
                    syncContext.Post(s => subscriber.OnEvent(eventToSend), null);
                }
                else
                {
                    toRemove.Add(weakSubscriber);
                }
            }

            if(toRemove.Any())
            {
                lock (_syncObject)
                {
                    foreach (WeakReference weakReference in toRemove)
                    {
                        subscribers.Remove(weakReference);
                    }
                }

            }
        }
    }
 public interface ISubscriber<in T>
    {
        void OnEvent(T message);
    }

    public class StatsCounter : ISubscriber<ScoreMessage>, ISubscriber<AllPlayersFinished>
    {
        public static readonly object _sync = new object();
        private Dictionary<string, int> _players;

        public StatsCounter()
        {
            _players = new Dictionary<string, int>();
        }


        public void OnEvent(ScoreMessage message)
        {
            lock (_sync)
            {
                var name = message.Sender.Name;
                Console.WriteLine(String.Format("{0} scored !", name));
                if(_players.ContainsKey(name))
                {
                    _players[name]++;
                }
                else
                {
                    _players.Add(name,0);
                }
            }
        }

        public void OnEvent(AllPlayersFinished message)
        {
            lock (_sync)
            {
                Console.WriteLine("Results : ");
                foreach (KeyValuePair<string, int> keyValuePair in _players)
                {
                    Console.WriteLine(String.Format("{0}\t scored {1} time(s)"),
                        keyValuePair.Key, keyValuePair.Value);
                }
                Console.ReadKey();
            }
        }
    }

    public class GameManager : ISubscriber<FinishMessage>
    {
        private readonly IEventAggregator _ea;

        private int _counter;

        public GameManager(IEventAggregator eventAggregator)
        {
            _ea = eventAggregator;
        }

        public void OnEvent(FinishMessage message)
        {
            _counter++;
            lock (StatsCounter._sync)
            {
                Console.WriteLine(String.Format("{0} Finished ! <-----",message.Sender.Name));
                Console.ReadKey();
            }
            if(_counter == 3)
                _ea.Publish(new AllPlayersFinished());
        }
    }
public interface IPlayer
    {
        string Name { get; set; }
    }

    public class Player : IPlayer
    {
        private IEventAggregator _ea;

        public int Sum { get; set; }
        public string Name { get; set; }

        public Player(IEventAggregator eventAggregator, string name)
        {
            _ea = eventAggregator;
            Name = name;
        }

        public void Play()
        {
            Random r = new Random();
            while (Sum < 1000)
            {
                int num = (r.Next() % 20);
                Sum += num;
                if(Sum % 10 == 0)
                    _ea.Publish(new ScoreMessage{Sender = this});
                Thread.Sleep(10*num);
            }
            _ea.Publish(new FinishMessage() {Sender = this});
        }
    }
public class ScoreMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class FinishMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class AllPlayersFinished
    {
        
    }
class Program
    {
        static void Main(string[] args)
        {
            IEventAggregator eventAggregator = new SimpleEventAggregator();
            var player1 = new Player(eventAggregator, "Player 1 [POL]");
            var player2 = new Player(eventAggregator, "Player 2 [USA]");
            var player3 = new Player(eventAggregator, "Player 3 [RUS]");
            StatsCounter counter = new StatsCounter();
            eventAggregator.Subscribe(counter);
            GameManager manager = new GameManager(eventAggregator);
            eventAggregator.Subscribe(manager);

            Thread t1 = new Thread(player1.Play);
            Thread t2 = new Thread(player2.Play);
            Thread t3 = new Thread(player3.Play);
            
            Console.WriteLine("The game has started !");

            t1.Start();
            t2.Start();
            t3.Start();
        }
    }

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.