Skip to main content

Command Palette

Search for a command to run...

C# 发布-订阅模式

Published
2 min read
C# 发布-订阅模式

#CSharp #PubSub

发布-订阅模式

有些时候,发布-订阅模式也会被称为观察者模式,但其实,如果细分的话,这两者之间还是有些细微的差别的。

观察者模式是在主题者(Subject)内部,维护一个观察者(Observer)列表。然后当主题者的状态发生变更的时候,主题者可以通知观察者发生了变化。观察者可以主动访问主题者,获得变更。也可以在通知观察者的时候,直接带上变更的消息。

在观察者模式中,主题者和观察者还是存在部分的耦合。因此,我们可以引入一个中介,来接触这种耦合。

类 A(发布者)发生了变化,将这个变化以消息或者事件的形式,发送给中介类,然后中介类将这个消息,转发给订阅这个消息的类 B(订阅者),这样发布者和订阅者并不直接产生依赖,他们都仅依赖于中介类(这个类可以是一个消息队列,也可以是你自定义的一个类型)。

代码

发布者基类:

public class EventPublisher  
{  
    private CustomEventHub EventHub { get; }  

    public EventPublisher()  
    {
        EventHub = CustomEventHub.GetInstance();  
    }  

    public void PublishEvent<T>(EventType eventType, T value)  
    {
        EventHub.Publish(new CustomEvent { Type = eventType, Value = value});  
    }
}

订阅者基类:

public class EventSubscriber
{
    private CustomEventHub EventHub { get; }

    public EventSubscriber()
    {
        EventHub = CustomEventHub.GetInstance();
    }

    public void Subscribe(EventType eventType, Action<CustomEvent> action)
    {
        EventHub.AddSubscriber(eventType, action);
    }
}

中介类:(为保证只有一个,这里额外加入了单例模式)

public class CustomEventHub
{
    private static readonly CustomEventHub Instance = new();
    private readonly Dictionary<EventType, IList<Action<CustomEvent>>> _eventTypeAndSubscribers = new();

    public static CustomEventHub GetInstance() => Instance;

    private CustomEventHub() { }

    public void Publish(CustomEvent customEvent)
    {
        var eventType = customEvent.Type;
        if (!_eventTypeAndSubscribers.ContainsKey(eventType)) { return; }

        var subscribers = _eventTypeAndSubscribers[eventType];
        foreach (var subscriberAction in subscribers)
        {
            subscriberAction.Invoke(customEvent);
        }
    }

    public void AddSubscriber(EventType eventType, Action<CustomEvent> action)
    {
        if (_eventTypeAndSubscribers.ContainsKey(eventType))
        {
            _eventTypeAndSubscribers[eventType].Add(action);
        }
        else
        {
            _eventTypeAndSubscribers.Add(eventType, new List<Action<CustomEvent>> { action });
        }
    }
}

自定义事件:

public class CustomEvent
{
    public EventType Type { get; set; }
    public object Value { get; set; }
}

事件类型:

public enum EventType
{
    TimeChanged
}

实际应用

时间变化时,发出事件通知:

public class TimeProvider : EventPublisher
{
    private Timer _timer = null!;

    public double Interval { get; set; }
    public DateTime CurrentTime { get; set; }

    public void Init()
    {
        CurrentTime = new DateTime(1900, 1, 1);
        _timer = new Timer(Interval)
        {
            AutoReset = true
        };
        _timer.Elapsed += OnTimeChanged;
    }

    private void OnTimeChanged(object? sender, ElapsedEventArgs e)
    {
        Console.Write($"TimeChanged From {CurrentTime :HH:mm:ss}");
        CurrentTime = CurrentTime.Add(TimeSpan.FromMilliseconds(Interval));
        Console.WriteLine($" To {CurrentTime :HH:mm:ss}");
        PublishEvent(EventType.TimeChanged, CurrentTime);
    }

    public void Start()
    {
        _timer.Enabled = true;
        GC.KeepAlive(_timer);
    }

    public void Stop()
    {
        _timer.Enabled = false;
    }
}

收到时间变化的事件后,打印一个“Hello World”:

public class SimulatorSystem : EventSubscriber
{
    private CustomEventHub _eventHub;

    public void Init()
    {
        Subscribe(EventType.TimeChanged, OnTimeChanged);
    }

    private void OnTimeChanged(CustomEvent customEvent)
    {
        if (customEvent.Type != EventType.TimeChanged) { throw new ArgumentException("Invalid event type"); }
        Console.WriteLine("Hello World");
    }
}
80 views

More from this blog

你愿意相信机器,还是人

春节回家,跟老爸一起刷了小破球2。 之后忽然想到了这个话题:你愿意相信机器,还是人 (一) 春节的时候,陪老爸去配了一副眼镜。 因为前不久我也正好花重金配了一副,在之前做了很多的功课,诸如眼镜的前倾角、面弯、镜间距、单眼瞳高、单眼瞳距之类的知识,了解了一些配镜过程中应该注意的事项。 老家是一个小县城,虽然眼镜店遍地都是,但专业的还真没几个。在配镜的过程中,能注意到这些细节的眼镜店几乎没有。蔡司专业的三维定位的仪器,更是全县城都没有。 当我提出需要仪器辅助测量这些参数的时候,一个配镜师傅跟我说,...

Jan 30, 20231 min read2
你愿意相信机器,还是人

Cache 缓存三问

哎,我一个个人项目基本上都没有超过过两位数的QPS的人,面试你天天问我缓存的这些问题,你们礼貌吗? 缓存穿透 是啥 当请求试图访问一个在缓存和数据库都不存在的 key时,因为缓存没有,所以请求会直接转发到数据库上。然而数据库里面也没有这个 key 的记录,所以也就没有办法将这个数据写入缓存。当下一次同样的请求来了的时候,还是会转发到数据库上。 在这种情况下,缓存基本上也就没用了,就像被穿透了一样,请求每次都会走到数据库,流量大时数据库可能会扛不住。 咋整 对请求进行校验。一些明显不合理的参数直...

Jun 17, 20221 min read18
Cache 缓存三问

C# 托管代码和垃圾回收

什么是托管代码 托管代码就是执行过程交由运行时管理的代码。运行时一般是指 CLR,公共语言运行时。 CLR 负责提取托管代码、将其编译成机器代码,然后执行它。除此之外,运行时还负责自动内存管理、安全边界、类型安全等等。 如果在 .Net 里面直接调用 C/C++ 程序,此类代码也称为“非托管代码”。在非托管代码的环境中,操作系统将程序加载进内存,然后调用内部的二进制代码,所以从内存管理到安全等诸多因素都需要程序员自己处理。 正常使用 .Net 编写的代码,会先编译成中间语言 (IL)。执行的...

Jun 14, 20221 min read43
C# 托管代码和垃圾回收

NightBack

7 posts