意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。
结构
动机
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。
适用性
- 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用;
- 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变;
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
优缺点
- 目标和观察者间的抽象耦合(接口)。目标只知道有一系列的观察者,但不知道它们所属的具体类;
- 支持广播通信。目标(Subject)发送的通知被自动广播给所有已向该目标登记的所有对象(Observer);
- 意外的更新。如果一个观察者(Observer)误操作目标(Subject)的状态,可能会导致其他观察者连锁反应式的错误更新。
注意事项
- 当观察者(Observer)依赖多个目标(Subject)时,考虑扩展Update接口,把目标对象(Subject)作为识别参数。
// Subject class
public void Notify()
{
foreah(Observer o in Observers)
{
o.Update(this);
}
}
// ConcreteObserver class
public void Update(Subject subject)
{
if(subject is ConcreteSubject1)
{
// 来自目标1的通知
}
else if(subject is ConcreteSubject2)
{
// 来自目标2的通知
}
...
}
- 在通知机制的实现上,可以由目标(Subject)对象自动触发,或者由客户端手动触发。
// 示例:状态变更,目标(Subject)自动触发。
public class ConcreteSubject : Subject
{
private object state;
public object SetState(object state)
{
this.state = state;
this.Notify(); // 自动触发
}
public void Notify()
{
foreach(Observer o in Observers)
{
o.Update();
}
}
}
// 示例
public class App
{
public static void Main(string[] args)
{
ConcreteSubject subject = new ConcreteSubject();
subject.Attach(Observer); // 登记观察者
...
// 状态每一次变更,都会自动通知观察者
subject.SetState(newState1);
subject.SetState(newState2);
...
}
}
// 示例:客户端手动触发通知
public class ConcreteSubject
{
private object state;
public object SetState(object state)
{
this.state = state;
}
public void Notify()
{
foreach(Observer o in Observers)
{
o.Update();
}
}
}
// 示例
public class App
{
public static void Main(string[] args)
{
ConcreteSubject subject = new ConcreteSubject();
subject.Attach(Observer); // 登记观察者
...
// 设置完一系列状态后,一次性通知观察者(避免观察者繁琐更新)。
subject.SetState(newState1);
subject.SetState(newState2);
...
subject.Notify(); // 手动通知(容易遗忘)
}
}
- 确保目标(Subject)在触发通知之前,处于一致状态。特别是在子类集成Subject时容易发生,可用模板(Template)模式实现;
// 存在状态不一致的错误代码
public class ConcreteSubject : Subject
{
...
public override void SetState(object state)
{
base.Notify(); // 提前触发,导致状态不一致。
this.state = state;
}
}
更改为模板实现方式:
public class Subject
{
...
// 模板方法
public void SetState(object state)
{
this.InternalSetState(state);
this.Notify();
}
protected virtual void InternalSetState(object state)
{
this.state = state;
}
}
public class ConcreteSubject : Subject
{
...
// 子类只需要实现个性化的状态处理
protected override void InternalSetState(object state)
{
...
this.state = state;
}
}
- 可以扩展目标的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率。
public void Attach(Observer observer, InterestType interest);
示例
模拟两个不同类型的图形控件,分别显示当前的时间。
实现(C#)
using System;
using System.Threading;
using System.Collections.Generic;
// 目标主题基类
public class Subject
{
private readonly List<Observer> observers = new List<Observer>();
public void Attach(Observer o)
{
this.observers.Add(o);
}
public void Detach(Observer o)
{
this.observers.Remove(o);
}
public void Notify()
{
foreach(Observer o in this.observers)
{
o.Update(this);
}
}
}
// 观察者
public abstract class Observer
{
public abstract void Update(Subject theChangedSubject);
}
// 具体的目标主题,以3秒间隔发出通知
public class ClockTimer : Subject
{
private Timer timer;
public ClockTimer()
{
this.timer = new Timer(this.Tick, null, 0, 3000);
}
public void Tick(object state)
{
this.Now = DateTime.Now;
this.Notify();
}
public DateTime Now { get; private set;}
}
// 模拟时钟控件1
public class DigitalClock : Observer
{
private readonly ClockTimer subject;
public DigitalClock(ClockTimer subject)
{
this.subject = subject;
this.subject.Attach(this); // 注册监听
}
public override void Update(Subject theChangedSubject)
{
// 确认是否为目标监听对象
if(this.subject == theChangedSubject)
{
Console.WriteLine("1.DigitalClock : " + this.subject.Now);
}
}
}
// 模拟时钟控件2
public class AnalogClock : Observer
{
private readonly ClockTimer subject;
public AnalogClock(ClockTimer subject)
{
this.subject = subject;
this.subject.Attach(this); // 注册监听
}
public override void Update(Subject theChangedSubject)
{
// 确认是否为目标监听对象
if(this.subject == theChangedSubject)
{
Console.WriteLine("2. AnalogClock : " + this.subject.Now);
}
}
}
public class App
{
public static void Main(string[] args)
{
ClockTimer timer = new ClockTimer();
DigitalClock digitalClock = new DigitalClock(timer);
AnalogClock analogClock = new AnalogClock(timer);
Console.WriteLine("please enter any key to exit..\n");
Console.Read();
}
}
// 控制台输出:
// please enter any key to exit..
// 1.DigitalClock : 2017/6/17 22:37:24
// 2. AnalogClock : 2017/6/17 22:37:24
// 1.DigitalClock : 2017/6/17 22:37:27
// 2. AnalogClock : 2017/6/17 22:37:27
// 1.DigitalClock : 2017/6/17 22:37:30
// 2. AnalogClock : 2017/6/17 22:37:30
// 1.DigitalClock : 2017/6/17 22:37:33
// 2. AnalogClock : 2017/6/17 22:37:33