上一篇说到了观察者模式较为传统的用法,这篇准备分享点流行的,不过在开始新内容之前,我们不妨先思考一下两种场景,一个是报社订阅报纸,另一个是在黑板上发公告,都是典型观察者模式应用场景,二者有何不同?
可以看到,二者有明显的区别。前者,观察者必须要注册到被观察者上才能接收通知;而后者,观察者和被观察者之间是相互完全陌生的。回顾一下我们在上一篇中举的例子,不难发现它其实类似第二种场景,狗叫并不知道谁会听见,而听的人也不是为了听狗叫,他仅仅是在关注外界的动静,恰好听到了狗叫而已。但我们采用的是类似第一种场景的处理方式,显然并不合适。因此,也就自然而然的留下了两个问题:
dog.AddObserver(...)
C#
Dog
Animal
针对这两个问题,该怎么解决了?不妨再回顾一下之前学过的设计原则,看看哪里可以寻找突破口。
一番思索不难发现,主题类违背了合成复用原则,也就是我们常说的,HAS A比IS A更好。既然知道HAS A更好,我们为什么非得通过继承来实现功能的复用呢?更何况我们继承的还是个普通类。
HAS A
IS A
基于这种思路,我们可以试着把继承改成组合,不过在这之前,我们不妨一步到位,干脆再为Subject类定义一个抽象的接口,免得看着不舒服,毕竟面向抽象编程嘛:
Subject
public interface ISubject{ void AddObserver(IObserver observer); void RemoveObserver(IObserver observer); void Publish(EventData eventData);}public class Subject: ISubject{ private readonly IList<IObserver> _observers = new List<IObserver>(); public void AddObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } public void Publish(EventData eventData) { foreach (var observer in _observers) { observer.Update(eventData); } }}
public interface ISubject
{
void AddObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void Publish(EventData eventData);
}
public class Subject: ISubject
private readonly IList<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer)
_observers.Add(observer);
public void RemoveObserver(IObserver observer)
_observers.Remove(observer);
public void Publish(EventData eventData)
foreach (var observer in _observers)
observer.Update(eventData);
逻辑并没有任何改动,仅仅是实现了一个接口而已,这一步不做其实也没有关系。接下来该做什么应该也很清楚了,没错,就是组合到被观察者中去,也就是Dog和Son,下面是具体的实现:
Son
public class Dog{ private readonly ISubject _subject; public Dog(ISubject subject) { this._subject = subject; } public void Bark() { Console.WriteLine("遥闻深巷中犬吠"); _subject.Publish(new EventData { Source = this, EventType = "DogBark" }); }}public class Son : IObserver{ private readonly ISubject _subject; public Son(ISubject subject) { this._subject = subject; } public void Update(EventData eventData) { if (eventData.EventType == "DogBark") { Wakeup(); } } public void Wakeup() { Console.WriteLine("既而儿醒,大啼"); _subject.Publish(new EventData { Source = this, EventType = "SonCry" }); }}
public class Dog
private readonly ISubject _subject;
public Dog(ISubject subject)
this._subject = subject;
public void Bark()
Console.WriteLine("遥闻深巷中犬吠");
_subject.Publish(new EventData { Source = this, EventType = "DogBark" });
public class Son : IObserver
public Son(ISubject subject)
public void Update(EventData eventData)
if (eventData.EventType == "DogBark")
Wakeup();
public void Wakeup()
Console.WriteLine("既而儿醒,大啼");
_subject.Publish(new EventData { Source = this, EventType = "SonCry" });
修改的仅仅是被观察者,观察者不需要做任何改变。看到上面的调用,不知道大家有没有一种熟悉的感觉呢?没错,这里的使用方式像极了微服务中常用的事件总线EventBus,事实上,事件总线就是这么实现的,基本原理仅仅是观察者模式继承转组合而已。
EventBus
再看看调用的地方:
static void Main(string[] args){ ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); subject.AddObserver(wife); subject.AddObserver(husband); subject.AddObserver(son); dog.Bark();}
static void Main(string[] args)
ISubject subject = new Subject();
Dog dog = new Dog(subject);
Wife wife = new Wife();
Husband husband = new Husband();
Son son = new Son(subject);
subject.AddObserver(wife);
subject.AddObserver(husband);
subject.AddObserver(son);
dog.Bark();
将Dog与Subject之间的关系改为HAS A之后,实际的事件发出者和事件接收者之间多了一层,使得二者之间完全解耦了。这时,Dog可以继承自己的Animal基类了,并且也不用再做类似在Dog类中管理Wife、Husband、Son这么奇怪的事了,对观察者的管理交给总线来完成。
Wife
Husband
再来看看这时的类图长什么样子:
如果觉得复杂,可以不看Dog和Sun这两个节点,只看实线框中的部分,有没有发现就是前面简易版的观察者模式呢?被观察者还是Subject,只不过和Dog、Sun已经没什么关系了,这是多一层必然会导致的结果。到这里,其实已经完美实现需求了,Subject是原来的被观察者,但现在相当于事件总线,在程序启动的时候,将观察者全部注册到总线上就可以接收到总线上的事件消息了。
Sun
你以为这样就完了吗?其实并没有。再回到软件开发领域,我们知道,事件的触发可以发生在系统内部,也可以发生在系统之间。而前面无论哪种方式的实现,其实解决的都是内部问题,那如果需要跨系统该怎么办呢?直接调用的话,会像上篇当中的第一个实现一样,出现强耦合,只不过这时调用的不再是普通的方法,而是跨网络的API,而强耦合的也不再是类与类之间,而是系统与系统之间。并且随着事件数量的增多,也会使得调用链变得混乱不堪,难以管理。
为了解决这个问题,就需要在所有系统之外,加入一个中间代理的角色,所有发布者将事件消息按不同主题发送给代理,然后代理再根据观察者关注主题的不同,将消息分发给相应的观察者,当然,前提是发布者和观察者都提前在代理这里完成注册登记。
我们先模拟实现一个代理,当然,我这里只是通过单例模式实现一个简单的示例,真实情况会比这个复杂的多:
public class Broker{ private static readonly Lazy<Broker> _instance = new Lazy<Broker>(() => new Broker()); private readonly Queue<EventData> _eventDatas = new Queue<EventData>(); private readonly IList<IObserver> _observers = new List<IObserver>(); private readonly Thread _thread; private Broker() { _thread = new Thread(Notify); _thread.Start(); } public static Broker Instance { get { return _instance.Value; } } public void AddObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } private void Notify(object? state) { while (true) { if (_eventDatas.Count > 0) { var eventData = _eventDatas.Dequeue(); foreach (var observer in _observers) { observer.Update(eventData); } } Thread.Sleep(1000); } } public void Enqueue(EventData eventData) { _eventDatas.Enqueue(eventData); }}
public class Broker
private static readonly Lazy<Broker> _instance
= new Lazy<Broker>(() => new Broker());
private readonly Queue<EventData> _eventDatas = new Queue<EventData>();
private readonly Thread _thread;
private Broker()
_thread = new Thread(Notify);
_thread.Start();
public static Broker Instance
get
return _instance.Value;
private void Notify(object? state)
while (true)
if (_eventDatas.Count > 0)
var eventData = _eventDatas.Dequeue();
Thread.Sleep(1000);
public void Enqueue(EventData eventData)
_eventDatas.Enqueue(eventData);
这里通过单例模式定义了一个Broker代理类,实际情况下,这部分是由一个永不停机的MQ服务承担,主要包括四个部分组成:
Broker
Queue<EventData>
事实上,上述四个部分都应该针对不同的主题实现,也就是我们常常会提到的Topic,几乎所有的MQ都会有Topic的概念,为了简单,我们这里就不考虑了。
再来看看Subject的实现:
public interface ISubject{ void Publish(EventData eventData);}public class Subject: ISubject{ public void Publish(EventData eventData) { Broker.Instance.Enqueue(eventData); }}
Broker.Instance.Enqueue(eventData);
由于对IObserver的管理交给了Broker代理,因此这里就不需要再关注具体的观察者是谁,也不需要管理观察者了,只需要负责发布事件就行了。需要注意的是,事件消息发布给了Broker,后续的一切工作交给Broker全权处理,观察者依然不需要做任何代码上的修改。
IObserver
调用的地方涉及到的改变主要体现在观察者的注册上,毕竟管理者不再是Subject,而是交由Broker代理接管了:
static void Main(string[] args){ ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); Broker.Instance.AddObserver(wife); Broker.Instance.AddObserver(husband); Broker.Instance.AddObserver(son); dog.Bark();}
Broker.Instance.AddObserver(wife);
Broker.Instance.AddObserver(husband);
Broker.Instance.AddObserver(son);
乍一看,事情变得越来越复杂了,这里为了解决跨系统的问题,又套了一层,类图有点复杂,为避免混乱,我就不画了。不过好在思路的演进是清晰的,达到现在的结果,应该也不会觉得突兀,这个其实就是当前盛行的MQ的基本实现思路了。
通过前面一系列的改造,我们解决了不同场景下的事件处理问题。接下来,我们再次梳理一下观察者模式的整个演进过程,先看一张图:
这张图显示了观察者模式演进的不同阶段,主题与观察者之间的调用关系:
可以看出,他们都有各自的应用场景,并不能简单的说谁更先进,谁能替代谁。可以预见,观察者模式未来可能还会继续演进,去应对更多新的更复杂的场景。
既然观察者模式这么好用,那.Net框架中自然也会内置一些处理机制了。
delegate
event
IObserver<T>
IObservable<T>
这里就不列代码,以免喧宾夺主了,因为这不是本文的重点。而且前者太常用了,应该没什么人不会。而后者呢,不知道大家用的多不多,但其实我自己没怎么用,我更愿意根据不同的场景来定义语义更明确的接口,如ISender用于发送,IProducer用于生产,IListener用于监听,IConsumer用于消费等。
ISender
IProducer
IListener
IConsumer
事件无处不在,毫不夸张的说,整个世界的运转都是由事件驱动的。因此观察者模式也是无处不在的。我们知道,设计模式经过这么多年的发展,已经有了很大的变化,有的下沉变成了某些语言的惯用法,例如后面会讲到的迭代器模式,有些上升更偏向于架构模式,例如前面讲过的外观模式。甚至有的被淘汰,例如备忘录模式。但是观察者模式却是唯一一个向上可用于架构设计,向下被实现为惯用法,中间还能重构代码,简直无处不在,无所不能。并且可以预见,未来也必然是经久不衰。
说的有点夸张了,不过也确实说明观察者模式再怎么重视也不为过了!
源码链接
原文链接:http://www.cnblogs.com/FindTheWay/p/14772101.html
本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728