Реализуем шаблон 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}");
        }
    }