在日常生活中,有很多需要我们关注的事务(比如,股市,楼市等),这些事务我们可以称之为主题或者叫信息发布者,观察主题的目的是想了解主题的变化(消息)。一种方法当然是采用盯人策略,但这种方法有个固有的缺点,就是你盯住主题的时候,无法干其他事情,如果需要了解的主题比较多,这种办法就很麻烦了;另外一种就是主题广播,我想听的时候我就去听,不想听的时候我就不听,这种方式的好处就是可以使得观察者不用盯住主题,但缺点是如果信息发布者的信息发布是不固定的,观察者(信息接收者)可能会漏掉信息。这两种方式都各有利弊,局域网中的信息传送采用的其实就是主题广播方式,只是接收者也是盯人战术。还有一种方式就是信息发布者提供一个注册机制,如果你要关注这个主题,就可以登记,如果主题有消息时,就按协定好的方式来通知注册的观察者。这就是观察者模式的现实生活中的原型。
《设计模式》中对观察模式的定义是:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。简图如下:
示例代码如下:
using System;
using System.Collections;
namespace DesignModelStudy
{
#region 观察者模式
/* 名称:Observer 观察者模式
* 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
* 适用性:
* 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
* 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
* 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
*
* 结构:
* | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄| | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
* | Subject | observers | Observer |
* |------------------|--------------------------------→|------------------|
* | Attach(Observer) | | Update() |
* | Detach(Observer) | | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄| |__________________|
* | Notify() ○-----|-| for all o in observers{ | |
* |__________________| | o->Update() } | |
* △ |__________________________| |
* | |
* | |
* | △
* | |
* | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄| |
* | ConcreateSubject | subject | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
* |------------------------------|←---------------------| ConcreateObserver |
* | GetState() ○------------|-| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄||------------------------| | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
* | SetState() | |return subjectState || Update() ○---------|----| observerState=subject->GetState() |
* |------------------------------| |____________________||------------------------| |______________________________________|
* | subjectState | | observerState |
* |______________________________| |________________________|
*
*
*
*
* */
#endregion
///
/// 抽象观察者,定义了一种主题与具体观察者之间进行通信的标准,有两种含义:1,为主题定义了所能接收其注册的观察者的接口标准,
/// 2定义了主题发布信息时与观察者之间通信的接口方法(观察者提供给主题进行传送消息的方法)。
///
public abstract class Observer_Observer
{
public abstract void Update();
}
///
/// 抽象主题,主要实现主题注册和通知的机制(即观察者模式的基本机制的实现)
/// 这样做的好处是只要是继承自抽象主题的子类都可以应用观察者模式。有利于主题的变化和扩展。
///
public class Observer_Subject
{
private ArrayList observers = new ArrayList();
//注册
public virtual void Attach(Observer_Observer observer)
{
if(observers.IndexOf(observer)<0)
{
observers.Add(observer);
}
}
//取消注册
public virtual void Detach(Observer_Observer observer)
{
observers.Remove(observer);
}
//通知
public virtual void Notify()
{
foreach( Observer_Observer o in observers)
{
o.Update();
}
}
}
///
/// 具体的主题
///
public class Observer_ConcreateSubject : Observer_Subject
{
private string _Name;
private double _Value;
public Observer_ConcreateSubject(string name,double val ) : base()
{
this._Name = name;
this._Value = val;
}
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
public double Value
{
get
{
return _Value;
}
set
{
_Value =value;
}
}
}
///
/// 具体观察者1
///
public class Observer_ConcreateObserverA : Observer_Observer
{
private string _Name;
private double _Value;
private Observer_ConcreateSubject subject;
public override void Update()
{
_Name = this.ToString()+"."+subject.Name;
_Value = subject.Value;
System.Windows.Forms.MessageBox.Show(_Name+":"+_Value.ToString());
}
public Observer_ConcreateObserverA(Observer_ConcreateSubject s)
{
this.subject = s;
}
}
///
/// 具体观察者2
///
public class Observer_ConcreateObserverB : Observer_Observer
{
private string _Name;
private double _Value;
private Observer_ConcreateSubject subject;
public override void Update()
{
_Name = this.ToString()+"."+subject.Name;
_Value = subject.Value;
System.Windows.Forms.MessageBox.Show(_Name+":"+_Value.ToString());
}
public Observer_ConcreateObserverB(Observer_ConcreateSubject s)
{
this.subject = s;
}
}
public class Observer_Client
{
public static void Test()
{
Observer_ConcreateSubject subject = new Observer_ConcreateSubject("Item",1000);
Observer_Observer o1= new Observer_ConcreateObserverA(subject);
Observer_Observer o2= new Observer_ConcreateObserverB(subject);
subject.Attach(o1);
subject.Attach(o2);
subject.Notify();
}
}
}
考虑到主题的多样性和观察者的关注点和角度的多样性,上面的观察者模式具有很大的局限性:
1)抽象主题采用抽象类来实现,就会导致一个主题只能接收一种观察者,如果主题本身就是有类层次结构的就要求系统必须支持多继承方式,这显然是个很过分的要求。抽象主题采用接口方式实现可以解决上述问题,这是一种改进;但又造成一个问题,因为从设计来讲,我们希望对象的责任能够比较单一,如果采用接口实现,那么对观察者的注册管理就必须由具体主题类来实现,这显然增加了主题类的责任(而且是额外的),一个解决办法就是将注册管理委托给一个对象类来进行,我们可以称之为注册管理对象,这样一来好处有2:一是使得主题的责任单一,二是解除了主题对观察者的依赖,这时候的多个主题之间不需要有公共的接口。当然,主题需要维护一个对注册类的引用,但即使是这样,好处还是很多,而且可扩展性变得更强;
2)在实际应用中不同的观察者对于关注的主题和侧重点是不一样的,观察者必须知道具提要观察的主题的结构,这就造成了观察者对主题的依赖。这种依赖即使观察者模式的局限,也是观察者模式在很大程度上无法避免的情况。
3)增加注册管理对象后,除了分离了一部分主题的责任,使得主题责任单一,并对观察者解耦外,还有一个好处就是可以在注册对象增加针对观察者的额外功能(比如观察者计数等)。但如果在不需要对观察者进行管理的情况,无论是改进前的还是改进后的观察者模式都有一个不利的地方,就是增加了开销,因为都需要维护一个观察者对象池。改进的办法就是利用事件来实现。利用事件来实现观察者模式有两个好处,一是只要一个事件类型定义,不需要定义接口和实现接口,二是不需要维护观察者对象池。如果将主题要发布的信息参数化,而且观察者除了要知道这个消息,并不需要了解主题的具体细节外,还可以解除观察者与主题之间的耦合关系。
下面是改进后的观察者模式的实现示例:
using System;
using System.Collections;
namespace DesignModelStudy
{
//观察者接口定义
public interface Observer1
{
//被观察者发生改变时会调用观察者的这个方法。
void Update(object subject);
}
///
/// 观察者聚合集,这样被观察者就知道了有那些观察者在注视它们,以便被观察者发生改变时好通知它们
/// 但这里仅仅是个接口,具体的管理有实现类完成。
///
public interface Subject1
{
//注册观察者
void AddObserver(Observer1 observer);
//取消注册
void RemoveObserver(Observer1 observer);
//实现该接口以通知所有的观察者.
void Notify(object realSubject);
}
///
/// 具体实现观察者管理的具体对象。
///
public class SubjectHelper : Subject1
{
private ArrayList observers = new ArrayList();
public void AddObserver(Observer1 observer)
{
observers.Add(observer);
}
public void RemoveObserver(Observer1 observer)
{
observers.Remove(observer);
}
public void Notify(object realSubject)
{
foreach(Observer1 observer in observers)
{
observer.Update(realSubject);
}
}
}
//注意这里的Album不从Subject或SubjectHelper继承,显示了另外一种观察者模式
//这样可以使得对象间的耦合度更小。
//这是被观察者对象
public class Album
{
private String name;
//被观察者要知道观察者管理对象,以便通过这个类实现对观察者的管理和通知。
private Subject1 playSubject = new SubjectHelper();
public Album(String name)
{ this.name = name; }
public void Play()
{
playSubject.Notify(this);
// code to play the album
}
public String Name
{
get { return name; }
}
public Subject1 PlaySubject
{
get { return playSubject; }
}
}
class Observer1_Client
{
//普通方式
public static void Test()
{
BillingService billing = new BillingService();
CounterService counter = new CounterService();
Album album = new Album("Up");
album.PlaySubject.AddObserver(billing);
album.PlaySubject.AddObserver(counter);
album.Play();
}
//利用事件来实现
public static void Test1()
{
BillingService billing = new BillingService();
CounterService counter = new CounterService();
Album_1 album = new Album_1("Up");
album.PlayEvent += new Album_1.PlayHandler(counter.Update);
album.PlayEvent += new Album_1.PlayHandler(billing.Update);
album.Play();
}
}
//具体的观察者类,每个观察者对被观察对象的关注角度是不同的,下面仅仅列出两种观察类
public class CounterService : Observer1
{
public void Update(object subject)
{
if(subject is Album_1)
GenerateCharge((Album_1)subject);
}
private void GenerateCharge(Album_1 album)
{
string name = album.Name;
//code to generate charge for correct album
System.Windows.Forms.MessageBox.Show("Counter:"+name);
}
}
public class BillingService : Observer1
{
public void Update(object subject)
{
if(subject is Album_1)
GenerateCharge((Album_1)subject);
}
private void GenerateCharge(Album_1 album)
{
string name = album.Name;
//code to generate charge for correct album
System.Windows.Forms.MessageBox.Show("Billing:"+name);
}
}
//事件方式实现.
public class Album_1
{
private String name;
public delegate void PlayHandler(object sender);
public event PlayHandler PlayEvent;
public Album_1(String name)
{ this.name = name; }
public void Play()
{
Notify();
// code to play the album
}
private void Notify()
{
if (PlayEvent != null)
PlayEvent(this);
}
public String Name
{
get { return name; }
}
}
}
后记:
上面的讨论基本都局限于观察者与主题之间的组织,没有讨论观察者和主题之间的通信(上面的模式基本都采用调用通信),实际上主题和观察者之间的通信方式很多,但这与具体的操作系统和网络有关。在通信方式可以支持的情况下,不仅可以实现消除观察者和主题之间的耦合关系,还可以让观察者和主题位于不同的地址空间。
典型应用:MVC模式,Delphi的数据感知控件和数据集,C#的控件和绑定数据源等。