一、简介(Brief Introduction)
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,会通知所有观察者对象,使它们能够自动更新自己。
例子:
用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
业务数据是被观察对象,用户界面是观察者。观察者和被观察者之间存在“观察”的逻辑关联,当被观察者发生改变的时候,观察者就会观察到这样的变化,并且做出相应的响应。如果在用户界面、业务数据之间使用这样的观察过程,可以确保界面和数据之间划清界限,假定应用程序的需求发生变化,需要修改界面的表现,只 需要重新构建一个用户界面,业务数据不需要发生变化。
二、模式分析(Analysis)
目标(Subject):目标知道它的观察者。可以有任意多个观察者观察同一个目标。 提供注册和删除观察者对象的接口。
具体目标(ConcreteSubject): 将有关状态存入各ConcreteObserver对象。
观察者(Observer): 为那些在目标发生改变时需获得通知的对象定义一个更新接口。当它的状态发生改变时, 向它的各个观察者发出通知。
具体观察者(ConcreteObserver): 维护一个指向ConcreteSubject对象的引用。存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致。
三、案例分析(Example)
实现观察者模式有很多形式,比较直观的一种是使用一种“注册——通知——撤销注册”的形式。
观察者
(Observer)将自己注册到被观察对象(Subject)中,被观察对象将观察者存放在一个容器(Container)里。
被观察
被观察对象发生了某种变化(如图中的SomeChange),从容器中得到所有注册过的观察者,将变化通知观察者。
撤销观察
观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。
观察者将自己注册到被观察者的容器中时,被观察者不应该过问观察者的具体类型,而是应该使用观察者的接口。这样的优点是:假定程序中还有别的观察者,那么只要这个观察者也是相同的接口实现即可。一个被观察者可以对应多个观察者,当被观察者发生变化的时候,他可以将消息 一一通知给所有的观察者。基于接口,而不是具体的实现——这一点为程序提供了更大的灵活性。
namespace 观察者模式中
{
class Program
{
static void Main(string[] args)
{
ConcreteSubject s=new ConcreteSubject();
s.Attach(new ConcreteObserver(s,"X"));
s.Attach(new ConcreteObserver(s,"Y"));
s.Attach(new ConcreteObserver(s,"Z"));
s.SubjectState="ABC";
s.Notify();
Console.Read();
}
}
//抽象通知者
abstract class Subject
{
private IList<Observer> observers = new List<Observer>();
//增加观察者
public void Attach(Observer observer)
{
observers.Add(observer);
}
//删除观察者
public void Detach(Observer observer)
{
observers.Remove(observer);
}
//通知者
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
//抽象观察者
abstract class Observer
{
public abstract void Update();
}
//具体通知者
class ConcreteSubject:Subject
{
private string subjectState;
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}
//具体观察者
class ConcreteObserver:Observer
{
private string name;
private string observerState;
private ConcreteSubject subject;
public ConcreteObserver(ConcreteSubject subject,string name)
{
this.subject=subject;
this.name=name;
}
public override void Update()
{
observerState=subject.SubjectState;
Console.WriteLine("观察者{0}的新状态是{1}",name,observerState);
}
public ConcreteSubject Subject
{
get{return Subject;}
set{subject=value;}
}
}
}
四、解决的问题(What To Solve)
• 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
• 当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。
• 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。
五、优缺点(Advantage and Disadvantage)
优点:
1)观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
2)在观察目标和观察者之间建立一个抽象的耦合 :一个目标所知道的仅仅是它 有一系列观察者 , 每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。因为目标和 观察者不是紧密耦合的, 它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它 , 这样就保持了系统层次的完整。如果目标和观察者混在一块 , 那么得到的对象要么横贯两个层次 (违反了层次性), 要么必须放在这两层的某一层中(这可能会损害层次抽象)。
3)支持广播通信 :不像通常的请求, 目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣 ;它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
4) 观察者模式符合“开闭原则”的要求。
缺点:
1)如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2)如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3)观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
4) 意外的更新 因为一个观察者并不知道其它观察者的存在 , 它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外 , 如果依赖准则的定义或维护不当,常常会引起错误的更新 , 这种错误通常很难捕捉。
简单的更新协议不提供具体细节说明目标中什么被改变了 , 这就使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。
六、扩展(Extend)
针对观察模式的不足(抽象通知者还是依赖抽象观察则),可以引入事件委托解决。
委托是一种引用方法的类型。一旦为委托分配了方法,委托将于该方法具有完全相同的行为。委托方法使用可以像其他方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。
七、联系(Link)
1) 中介者模式Mediator: 通过封装复杂的更新语义 , ChangeManager充当目标和观察者之间的中介者。
2) 单例模式Singleton: ChangeManager可使用Singleton模式来保证它是唯一的并且是可全局访问
的。
八、总结(Summary)
通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。