[TOC]
示例
股票变化(C#实现)
监控某一个公司(Microsoft)的股票价格变化,可以有多种方式,通知的对象可以是投资者,或者是发送到移动设备,还有电子邮件等
一开始我们先不考虑Observer
模式,通过一步步地重构,最终重构为Observer
模式
现在有这样两个类:Microsoft
和Investor
,如下图所示:
1 | public class Microsoft |
客户端:
1 | class Program |
运行后结果如下:
1 | Notified Jom of Microsoft's change to ¥120 |
可以看到,这段代码运行并没有问题,也确实实现了我们最初的设想的功能,把Microsoft的股票价格变化通知到了Jom投资者那儿。
但是这里面出现了如下几个问题:
Microsoft和Investor之间形成了一种双向的依赖关系,即Microsoft调用了Investor的方法,而Investor调用了Microsoft类的属性。如果有其中一个类变化,有可能会引起另一个的变化。
当出现一种的通知对象,比如说是移动设备Mobile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Mobile
{
private string _no;
public Mobile(string no)
{
this._no = no;
}
public void SendData(Microsoft ms)
{
Console.WriteLine("Notified {0} of {1}'s " + "change to {2:C}", _no, ms.Symbol, ms.Price);
}
}这时候对应的Microsoft的类就应该改变为如下代码,在Microsoft类中增加Mobile,同时修改Update()方法使其可以通知到移动设备:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Microsoft
{
public void Update()
{
Investor.SendData(this);
Mobile.SendData(this);
}
public Mobile Mobile { get; set; }
public Investor Investor { get; set; }
public String Symbol { get; set; }
public double Price { get; set; }
}
显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的。
对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消Microsoft和具体的通知对象之间依赖。
1 | public interface IObserver |
可以看到,我们在降低两者的依赖性上已经迈进了一小步,正在朝着弱依赖性这个方向变化。在Microsoft类中已经不再依赖于具体的Investor,而是依赖于接口IObserver。
但同时我们看到,再新出现一个移动设备这样的通知对象,Microsoft类仍然需要改变,对此我们再做如下重构,在Microsoft中维护一个IObserver列表,同时提供相应的维护方法。
此时的实现
1 | public class Microsoft |
客户端
1 | class Program |
走到这一步,已经有了Observer模式的影子了,Microsoft类不再依赖于具体的Investor,而是依赖于抽象的IOberver。
存在着的一个问题是Investor仍然依赖于具体的公司Microsoft,况且公司还会有很多IBM,Google等,解决这样的问题很简单,只需要再对Microsoft类做一次抽象。如下图所示:
此时的实现
1 | public interface IObserver |
客户端:
1 | class Program |
到这里我们可以看到,通过不断的重构,不断地抽象,我们由一开始的很糟糕的设计,逐渐重构为使用Observer模式的这样一个方案。
在这个例子里面,IOberser充当了观察者的角色,而Stock则扮演了主题对象角色,在任何时候,只要调用了Stock的Update()方法,它就会通知它的所有观察者对象。
同时可以看到,通过Observer模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。
.NET中的观察者模式
利用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
因为在上面的示例中我们可以看到,虽然取消了直接耦合,但是又引入了不必要的约束(暂且这么说吧)。即那些子类必须都继承于主题父类,还有观察者接口等。
上面的例子简单的用事件和委托实现如下:
1 | class Program { |
把无返回值委托换成action
1 | class Program |
钓鱼(C#实现)
鱼竿是被观察者,
铃铛是通知工具,
垂钓者是观察者。
鱼儿咬钩,鱼竿通过铃铛通知垂钓者收钩。
先来定义鱼的品类枚举:
1 | public enum FishType |
接下来申明一个钓鱼工具的抽象类,维护订阅者列表,并负责循环通知订阅者。
1 | /// <summary> |
鱼竿的实现,这里用随机数模拟鱼儿咬钩:
1 | /// <summary> |
定义简单的观察者接口:
1 | /// <summary> |
垂钓者实现观察者接口,并定义了Name
,FishCount
属性:
1 | /// <summary> |
测试:
1 | class Program |
用委托实现
有了委托,我们就不再需要定义专门的抽象被观察者对象了,直接实现鱼竿:
1 | /// <summary> |
因为被观察者定义了委托,我们也没必要定义专门的观察者接口,只需要在具体的观察者中实现对应的委托即可。
1 | /// <summary> |
客户端:
1 |
|
进制转换器(python实现)
1 | """http://code.activestate.com/recipes/131499-observer-pattern/""" |
登录网站模块(JavaScript实现)
JavaScript中的订阅-发布模式和别的语言(比如Java)中的实现还是有区别的.
在Java中,通常会把订阅者对象自身当成引用传入发布者对象中,同时订阅者对象还需提供一个名为诸如update
的方法,供发布者对象在合适的时候调用
在JavaScript中,用注册回调函数的形式来代替传统的发布-订阅模式,显得更加优雅和简单
注:在这里的回调其实就相当于例1中的事件委托stock.NotifyEvent += new NotifyEventHandler (investor.SendData);
.NET使用事件封装了发布订阅模式,语法糖更易用,但是不可能达到解释型语言的灵活性
假如我们正在开发一个商城网站,网站里有header
头部,nav
导航,消息列表,购物车等模块.这几个模块渲染有一个共同的前提条件,就是必须先用ajax
异步请求获取用户的登录信息
1 | login.succ(function(data) { |
header,nav等各个模块和用户信息产生了强耦合,方法名也不能随意再改变
等到有一天,项目中增加了一个收货地址管理的模块,则必须在原来的逻辑中增加
1 | address.refresh() |
用发布订阅模式重写之后,登录模块不用关心业务模块究竟做什么,也不想了解内部细节
1 | $.ajax('http://xxx.com?login', function(data) { |
我们可以随时更改setAvatar
方法名
如果某天需求增加了刷新收货列表的行为,只需要增加监听消息的方法即可
1 | var address = (function() { //address模块 |
事件管理器(JavaScript实现)
使用发布订阅模式写一个事件管理器,可以实现如下方式调用
1 | EventManager.on('text:change', function(val){ |
1 | //ES5 |