読者です 読者をやめる 読者になる 読者になる

No more Death March

あるSEのチラシの裏 C# WPF

インターフェースを考える(6)IEvent

以前書いたドメインイベントの実装について、
インターフェースの側から考える。

nomoredeathmarch.hatenablog.com
nomoredeathmarch.hatenablog.com

まず作成したインターフェースについて復習

まず、イベントを表すクラスのインターフェースIEvent

namespace Nmdm.Events
{
    public interface IEvent
    {
    }
}

あくまで印をつけるためのマーカー

続いてそのインターフェースを受け取るIHandler

namespace Nmdm.Events
{
    public interface IHandler
    {
        void Handle(IEvent value);
        bool CanHandle(IEvent value);
    }
}

こちらはパブリッシャーからの受信機構となるメソッド。

でここからが今回の本題、
Publisherクラスをstaticで実装したけども、これもインターフェースで抽象化してみる。

以下がインターフェースを抜き出したIPublisher

namespace Nmdm.Events
{
    public interface IPublisher
    {
        void Publish(IEvent e);
        void Subscribe(IHandler handler);
        void Unsubscribe(IHandler handler);
    }
}

staticじゃなくなったことで単体テストがしやすくなったけど、
半面、どこからでもイベント通知、購読することが出来なくなったわけだ。

ただ、staticクラスのように振舞わせたければシングルトンなりこのクラスをキャッシュするためだけのスタティッククラスを作れば良いわけだし、
それであれば通常のクラスとして設計すれば良いとも思える。

これを以前作ったPublisherクラスに実装させる。

using System;
using System.Collections.Generic;
using System.Threading;

namespace Nmdm.Events
{
    public class Publisher:IPublisher
    {
        private static Mutex Mutex { get; } = new Mutex();
        private static List<WeakReference<IHandler>> Subscribers { get; } 
            = new List<WeakReference<IHandler>>();
        public void Publish(IEvent e)
        {
            try
            {
                Mutex.WaitOne();
                if (e == null) throw new ArgumentNullException("e");
                var list = new List<IHandler>();
                list.AddRange(Subscribers.ConvertAll<IHandler>(x => ToStrong(x)));
                list.RemoveAll(x => x == null || !x.CanHandle(e));
                list.ForEach(x => x.Handle(e));
                Subscribers.RemoveAll(x => ToStrong(x) == null);
            }
            finally
            {
                Mutex.ReleaseMutex();
            }
        }

        private static IHandler ToStrong(WeakReference<IHandler> obj)
        {
            IHandler ret;
            obj.TryGetTarget(out ret);
            return ret;
        }

        public void Subscribe(IHandler subscriber)
        {
            try
            {
                Mutex.WaitOne();
                if (subscriber == null) throw new ArgumentNullException("subscriber");
                if (Subscribers.Contains(new WeakReference<IHandler>(subscriber))) return;
                Subscribers.Add(new WeakReference<IHandler>(subscriber));
            }
            finally
            {
                Mutex.ReleaseMutex();
            }

        }

        public void Unsubscribe(IHandler subscriber)
        {
            try
            {
                Mutex.WaitOne();
                if (subscriber == null) throw new ArgumentNullException("subscriber");
                Subscribers.RemoveAll(x => subscriber.Equals(ToStrong(x)));
            }
            finally
            {
                Mutex.ReleaseMutex();
            }

        }
    }
}