一、概述

对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。GOF 《设计模式》

这样的依赖关系过于紧密,软件不能很好地适应变化。使用面向对象技术,我们可以将这种依赖关系弱化,即降低耦合。


二、生活举例

1、我们每个人都有银行卡,通常会有一项业务,余额变更通知。这个便很好地体现了观察者模式。监控银行卡内余额的变化,当我们取钱,转账等原因导致余额变化时,系统会自动采用多种方式告知我们余额情况,可以是短信通知,可以是Email通知,也可以寄账单等等。

2、报社、订阅系统和订报人之间的关系,订报人通过订阅系统订报,一旦报社有新的报纸,订阅系统就会派人送或者邮寄给订报人新的报纸

3、网上商店,如果用户关注了某一商品,一旦这一商品的信息有变化,系统会自动发邮件或手机信息通知所有关注这一商品的用户。


三、实现思路

实现观察者模式有很多形式,比较直观的一种是使用一种“注册——通知——撤销注册”的形式。


四、类图



五、注意点

观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则。无论是观察者“观察”观察对象,还是被观察者将自己的改变“通知”观察者,都不应该直接调用。


观察者将自己注册到被观察者的容器中时,被观察者不应该过问观察者的具体类型,而是应该使用观察者的接口。这样的优点是:假定程序中还有别的观察者,那么只要这个观察者也是相同的接口实现即可。一个被观察者可以对应多个观察者,当被观察者发生变化的时候,他可以将消息一一通知给所有的观察者。基于接口,而不是具体的实现——这一点为程序提供了更大的灵活性。


六、实例

说明:下面我们通过一个例子循序渐进的介绍观察者模式。看观察者模式是如何应对需求变化的。当然这个例子是理想化中的实例,可能有些牵强,只为说明问题

语言:C#


需求1.0:《学校机房收费系统》,当学生刷卡下机强制其下机的时候,这个时候将引发两个变化,

1、更改卡内余额(更改学生信息表的余额) 2、使学生上机状态变为下线(删除正在上机表中的学生信息)


代码1.0实现:


using System;

namespace 观察者模式实例一
{

class Program
{
static void Main(string[] args)
{
Down dow = new Down();

OffLine off = new OffLine(dow);
UpdateBalance upd=new UpdateBalance(dow);

dow.Ol =off;
dow.Ub = upd;

dow.Action = "刷卡";
dow.Notify();
}
}
class Down
{
private UpdateBalance ub;
private OffLine ol;
private string action;

public UpdateBalance Ub
{
set { ub = value; }
}
public OffLine Ol
{
set { ol = value; }
}

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//注意这里,Down调用了UpdateBalance的方法和OffLine的方法
public void Notify()
{
ub.Update();
ol.Update();
}
}

class UpdateBalance
{
private Down dn;

public UpdateBalance(Down dn)
{
this.dn = dn;
}
//注意这里,UpdateBalance调用了Down类的属性
public void Update()
{
Console.WriteLine("{0}!该卡要下机,更新余额!", dn.Action);
}
}

class OffLine
{
private Down dn;

public OffLine(Down dn)
{
this.dn = dn;
}
//注意这里,OffLine调用了Down类的属性
public void Update()
{
Console.WriteLine("{0}!该卡要下机,删除正在上机表中的该卡信息!", dn.Action);
}
}
}


运行结果:

刷卡!该卡要下机,更新余额!

刷卡!该卡要下机,删除正在上机表中的该卡信息!


可以看到,这段代码实现了我们最初的功能,把打卡通知到了更新余额,变更在线状态。但

是这里面出现了如下几个问题:


1.类Down和类UpdateBalance之间形成了一种双向的依赖关系,即Down调用了UpdateBalance的方法,而UpdateBalance调用了Down类的属性。说白了,就是如果有其中一个类变化,有可能会引起另一个的变化,耦合太紧密


2.(需求2.0)如果这个时候,校方觉得需要修改功能,当学生刷卡下机的时候,不仅仅要更改卡内余额(更改学生信息表的余额) 和使学生上机状态变为下线(删除正在上机表中的学生信息),还要保存学生的上机记录(把上机记录信息写入上机记录表)。

这时,根据上面的代码1.0,就需要在这个基础上,改动Down这个类,这就不符合“开闭”原则。


那么面对需求2.0,我们的代码该怎么做呢?


代码2.0

using System;
using System.Collections;
using System.Collections.Generic;

namespace 观察者模式实例一
{

class Program
{
static void Main(string[] args)
{
Down dn = new Down();
Observer up = new UpdateBalance(dn);
Observer ol = new OffLine(dn);
Observer sr=new SaveRec(dn);
dn.Attach(up);
dn.Attach(ol);
dn.Attach(sr);

dn.Action = "刷卡";
dn.Notify();
}
}

//通知者
class Down
{
private IList<Observer> observers = new List<Observer>();
private string action;

//增加观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Attach(Observer observer)
{
observers.Add(observer);
}

//减少观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Detach(Observer observer)
{
observers.Remove(observer);
}

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
foreach (Observer o in observers)
o.Update();
}
}

//抽象观察者
abstract class Observer
{
protected Down dn;
public Observer(Down dn)
{
this.dn = dn;
}

public abstract void Update();
}

//具体观察者
class UpdateBalance:Observer
{

public UpdateBalance(Down dn):base (dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,更新余额!", dn.Action);
}
}

//具体观察者
class OffLine:Observer
{
public OffLine(Down dn):base (dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,删除正在上机表中的该卡信息!", dn.Action);
}
}
//具体观察者
class SaveRec:Observer
{
public SaveRec(Down dn)
: base(dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,保存上机记录信息!", dn.Action);
}
}

}


可以看到,我们们建立了一个抽象观察者,让具体观察者继承它。即面对抽象观察者编程。以后无论有多少相似观察者,我们都可以继承抽象观察者。


可是,这个时候问题又来了,“变态”的校方在此基础上,又提出了新的功能,即不仅学生刷卡下机的时候,实现更改卡内余额(更改学生信息表的余额) 和使学生上机状态变为下线(删除正在上机表中的学生信息),保存学生的上机记录(把上机记录信息写入上机记录表)。而且在操作员强制其下机的时候,同样引起上述三种变化。(需求3.0


这时我们发现,仍然存在着的一个问题:观察者仍然依赖于具体的通知者Down,况且还会有其它通知者,需求3.0中就出现了一个新的通知者“强制下机”,如果以后再多出新的通知者,例如“断电下机”,解决这样的问题很简单,我们完全可以照葫芦画瓢,在需求2.0中,我们抽象出一个抽象观察者,显然这里我们需要抽象出一个抽象通知者。


代码3.0:

using System;
using System.Collections;
using System.Collections.Generic;

namespace 观察者模式实例一
{

class Program
{
static void Main(string[] args)
{
Informer dn = new Down();
Informer fdn = new ForceDown();

Observer ol = new OffLine(fdn);
Observer up = new UpdateBalance(fdn);
Observer sr = new SaveRec(fdn);

fdn.Attach(up);
fdn.Attach(ol);
fdn.Attach(sr);

fdn.Action = "强制下机";
fdn.Notify();
}
}

//通知者接口
interface Informer
{
void Attach(Observer observer);
void Detach(Observer observer);
void Notify();
string Action { get; set; }
}

//具体通知者
class Down : Informer
{
private IList<Observer> observers = new List<Observer>();
private string action;

//增加观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Attach(Observer observer)
{
observers.Add(observer);
}

//减少观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Detach(Observer observer)
{
observers.Remove(observer);
}

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
foreach (Observer o in observers)
o.Update();
}
}
//具体通知者
class ForceDown : Informer
{
private IList<Observer> observers = new List<Observer>();
private string action;

//增加观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Attach(Observer observer)
{
observers.Add(observer);
}

//减少观察者,这里面向抽象观察者编程,降低了与具体观察者的耦合
public void Detach(Observer observer)
{
observers.Remove(observer);
}

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
foreach (Observer o in observers)
o.Update();
}
}


//抽象观察者
abstract class Observer
{
protected Informer dn;
public Observer(Informer dn)
{
this.dn = dn;
}

public abstract void Update();
}

//具体观察者
class UpdateBalance:Observer
{

public UpdateBalance(Informer dn)
: base(dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,更新余额!", dn.Action);
}
}

//具体观察者
class OffLine:Observer
{
public OffLine(Informer dn)
: base(dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,删除正在上机表中的该卡信息!", dn.Action);
}
}
//具体观察者
class SaveRec:Observer
{
public SaveRec(Informer dn)
: base(dn)
{
}
public override void Update()
{
Console.WriteLine("{0}!该卡要下机,保存上机记录信息!", dn.Action);
}
}
}


这时候我们看到,无论我们增加通知者,还是增加观察者,都仅仅写具体类继承抽象类。通过取消直接依赖,改为间接依赖,降低了耦合度,在适应变化方面,符合开闭原则。这便是观察者模式的精华所在。


写到这里,不禁联想起以前写过的一篇关于“委托和事件”的文章,​

这里就不具体说了,不明白的可以看我这篇文章。


我认为利用委托和事件来实现观察者模式:一方面,更加的简单和优雅。另一方面,克服了观察者模式的先天不足,“抽象通知者”依然依赖“抽象观察者”,也就是说,如果没有抽象观察者这样的接口,通知就无法完成。而且每个观察者不一定都像上例中那样调用相同名字的Update方法。如果不是同名的方法,观察者模式便陷入困境。


让我们来看看,委托和事件是如何帮助观察者模式摆脱困境的。

根据需求3.0,优化代码3.0
 
代码4.0:


using System;

namespace 观察者模式实例一
{

class Program
{
static void Main(string[] args)
{
Down dn = new Down();
//ForceDown fdn = new ForceDown();

OffLine ol = new OffLine(dn);
UpdateBalance up = new UpdateBalance(dn);
SaveRec sr = new SaveRec(dn);

//OffLine ol = new OffLine(fdn);
//UpdateBalance up = new UpdateBalance(fdn);
//SaveRec sr = new SaveRec(fdn);
dn.Update += new EventHandler(ol.DelOnlineInfo);
dn.Update +=new EventHandler(up.ModifyBalance);
dn.Update +=new EventHandler(sr.SaveLineRec);

dn.Action = "刷卡下机";
//发出通知
dn.Notify();

//fdn.Action = "强制下机";
//fdn.Notify();

}
}

//通知者接口
interface Informer
{
void Notify();
string Action { get; set; }
}

//声明委托
delegate void EventHandler();

//具体通知者
class Down : Informer
{
//声明事件Update,类型为委托EventHandler
public event EventHandler Update;

private string action;

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
this.Update();
}
}

//具体通知者
class ForceDown : Informer
{
//声明事件Update,类型为委托EventHandler
public event EventHandler Update;

private string action;

//当前状态
public string Action
{
get { return action; }
set { action = value; }
}
//通知
public void Notify()
{
this.Update();
}
}

//具体观察者
class UpdateBalance
{
protected Informer dn;
public UpdateBalance(Informer dn)
{
this.dn = dn;
}
public void ModifyBalance()
{
Console.WriteLine("{0}!该卡要下机,更新余额!", dn.Action);
}
}

//具体观察者
class OffLine
{
protected Informer dn;
public OffLine(Informer dn)
{
this.dn = dn;
}
public void DelOnlineInfo()
{
Console.WriteLine("{0}!该卡要下机,删除正在上机表中的该卡信息!", dn.Action);
}
}
//具体观察者
class SaveRec
{
protected Informer dn;
public SaveRec(Informer dn)
{
this.dn = dn;
}
public void SaveLineRec()
{
Console.WriteLine("{0}!该卡要下机,保存上机记录信息!", dn.Action);
}
}

}

七、何时选用观察者模式

当抽象个体有两个互相依赖的方面时。封装这些方面在单独的对象内可允许程序员单独地去变更与重复使用这些类,而不会产生两者之间交互的问题。

当其中一个对象的变更会影响其他对象,却又不知道多少对象必须被同时变更时。

当对象应该有能力通知其他对象,又不应该知道其他对象的内部细节时。


观察者模式常用在MVC和三层架构中。在 MVC 中,观察者模式被用来降低 model 与 view的耦合程度。一般而言, model 的改变会触发通知其他身为观察者的 view 。


八、总结

通过观察者模式,把一对多对象之间的依赖关系的变得更为松散(解耦),使程序能更好地适应变化,符合开放-封闭原则,大大地提高了程序的可维护性和可扩展性。