Реализуем шаблон publisher/subscriber

В данной заметке представлен шаблон publisher/subscriber, особенность данной реализации которой является то, что сами подписчики могут быть издателями и иметь своих собственных подписчиков. Получается сообщение будет отправлено рекурсивно по всем подписчикам. Такая реализация по сути является шиной данных, при которой все получатели и издатели распределены в разных хранилищах(в самих подписчиках), что избавляет шину данных от контроля удалять и добавлять получателей и отправителей. Шина данных всего лишь знает о получателей на самой верхушке. Такую шину можно использовать при создании UI представлений, как альтернатива event-ам. Тогда на «верхушке» будут только контроллеры представлений.

    public interface IPublisher
    {
        void Notify<TMessage>(TMessage message);
        void Subscribe(ISubscriber subscriber);
        void Unsubscribe(ISubscriber subscriber);
        void Clear();
    }
    public interface ISubscriber
    {
    }
    public interface ISubscriber<TMessage> : ISubscriber
    {
        void Update(TMessage message);
    }
    public interface IPublishable
    {
        IPublisher Publisher { get; }
    }

    public class Publisher : IPublisher
    {
        private readonly HashSet<ISubscriber> _subscribers = new HashSet<ISubscriber>();
        public void Notify<TMessage>(TMessage message)
        {
            foreach (ISubscriber subscriber in _subscribers)
            {
                if (subscriber is ISubscriber<TMessage>)
                {
                    ((ISubscriber<TMessage>)subscriber).Update(message);
                }
                if (subscriber is IPublishable)
                {
                    ((IPublishable)subscriber).Publisher.Notify(message);
                }
            }
        }

        public void Subscribe(ISubscriber subscriber)
        {

            _subscribers.Add(subscriber);
        }

        public void Unsubscribe(ISubscriber subscriber)
        {
            if (subscriber is IPublishable)
            {
                ((IPublishable)subscriber).Publisher.Clear();
            }
            _subscribers.Remove(subscriber);
        }

        public void Clear()
        {
            foreach (var subscriber in _subscribers)
            {
                if (subscriber is IPublishable)
                {
                    ((IPublishable)subscriber).Publisher.Clear();
                }
            }
            _subscribers.Clear();
        }
    }

    public class DataBus : Publisher
    {
        private DataBus() { }
        private static DataBus _instance;
        public static DataBus Instance => _instance ?? (_instance = new DataBus());
    }

Используем так:

    public static class Program
    {
        static void Main(string[] args)
        {
            var mainView = new UIView("Main view");
            DataBus.Instance.Subscribe(mainView);

            var innerView = new UIView("Inner view");
            mainView.SetInnerView(innerView);

            Console.WriteLine("Notify main view and main view notifies inner view");
            DataBus.Instance.Notify(new TitleChanged { Title = "TITLE 1" });
            DataBus.Instance.Notify(new IdChanged { Id = 1 });
            Console.WriteLine("Unsubscribe main view and main view unsubscribes innerview");
            DataBus.Instance.Unsubscribe(mainView);
            Console.WriteLine("Notify main view once more");
            DataBus.Instance.Notify(new TitleChanged { Title = "TITLE 1" });
            DataBus.Instance.Notify(new IdChanged { Id = 2 });
            Console.WriteLine("DataBus has no subscribers!");
            Console.ReadKey();
        }
    }

    public class IdChanged
    {
        public int Id { get; set; }
    }

    public class TitleChanged
    {
        public string Title { get; set; }
    }

    public class UIView : ISubscriber<IdChanged>, ISubscriber<TitleChanged>, IPublishable
    {
        private readonly string _name;
        private int _id;
        private string _title;
        private UIView _innerView;

        public UIView(string name)
        {
            _name = name;
        }

        public IPublisher Publisher { get; } = new Publisher();

        public void SetInnerView(UIView view)
        {
            _innerView = view;
            Publisher.Subscribe(_innerView);
        }

        public void Update(TitleChanged message)
        {
            _title = message.Title;
            Console.WriteLine($"I am {_name}, my Title: {_title}");
        }

        public void Update(IdChanged message)
        {
            _id = message.Id;
            Console.WriteLine($"I am {_name}, my Id: {_id}");
        }
    }