意圖
定義對(duì)象間的一種一對(duì)多的依賴(lài)關(guān)系眼刃,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴(lài)它的對(duì)象都得到通知并自動(dòng)更新。
結(jié)構(gòu)
觀察者結(jié)構(gòu)圖
觀察者時(shí)序圖
動(dòng)機(jī)
將一個(gè)系統(tǒng)分割成一系列相互協(xié)作的類(lèi)有一個(gè)常見(jiàn)的副作用:需要維護(hù)相關(guān)對(duì)象間的一致性参淹。我們不希望為了維持一致性而使各類(lèi)緊密耦合,因?yàn)檫@樣降低了它們的可重用性乏悄。
適用性
- 當(dāng)一個(gè)抽象模型有兩個(gè)方面, 其中一個(gè)方面依賴(lài)于另一方面浙值。將這二者封裝在獨(dú)立的對(duì)象中以使它們可以各自獨(dú)立地改變和復(fù)用;
- 當(dāng)對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象, 而不知道具體有多少對(duì)象有待改變檩小;
- 當(dāng)一個(gè)對(duì)象必須通知其它對(duì)象开呐,而它又不能假定其它對(duì)象是誰(shuí)。換言之, 你不希望這些對(duì)象是緊密耦合的。
優(yōu)缺點(diǎn)
- 目標(biāo)和觀察者間的抽象耦合(接口)负蚊。目標(biāo)只知道有一系列的觀察者神妹,但不知道它們所屬的具體類(lèi)颓哮;
- 支持廣播通信家妆。目標(biāo)(Subject)發(fā)送的通知被自動(dòng)廣播給所有已向該目標(biāo)登記的所有對(duì)象(Observer);
- 意外的更新冕茅。如果一個(gè)觀察者(Observer)誤操作目標(biāo)(Subject)的狀態(tài)伤极,可能會(huì)導(dǎo)致其他觀察者連鎖反應(yīng)式的錯(cuò)誤更新。
注意事項(xiàng)
- 當(dāng)觀察者(Observer)依賴(lài)多個(gè)目標(biāo)(Subject)時(shí)姨伤,考慮擴(kuò)展Update接口哨坪,把目標(biāo)對(duì)象(Subject)作為識(shí)別參數(shù)。
// Subject class
public void Notify()
{
foreah(Observer o in Observers)
{
o.Update(this);
}
}
// ConcreteObserver class
public void Update(Subject subject)
{
if(subject is ConcreteSubject1)
{
// 來(lái)自目標(biāo)1的通知
}
else if(subject is ConcreteSubject2)
{
// 來(lái)自目標(biāo)2的通知
}
...
}
- 在通知機(jī)制的實(shí)現(xiàn)上乍楚,可以由目標(biāo)(Subject)對(duì)象自動(dòng)觸發(fā)当编,或者由客戶(hù)端手動(dòng)觸發(fā)。
// 示例:狀態(tài)變更徒溪,目標(biāo)(Subject)自動(dòng)觸發(fā)忿偷。
public class ConcreteSubject : Subject
{
private object state;
public object SetState(object state)
{
this.state = state;
this.Notify(); // 自動(dòng)觸發(fā)
}
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); // 登記觀察者
...
// 狀態(tài)每一次變更,都會(huì)自動(dòng)通知觀察者
subject.SetState(newState1);
subject.SetState(newState2);
...
}
}
// 示例:客戶(hù)端手動(dòng)觸發(fā)通知
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); // 登記觀察者
...
// 設(shè)置完一系列狀態(tài)后臊泌,一次性通知觀察者(避免觀察者繁瑣更新)鲤桥。
subject.SetState(newState1);
subject.SetState(newState2);
...
subject.Notify(); // 手動(dòng)通知(容易遺忘)
}
}
- 確保目標(biāo)(Subject)在觸發(fā)通知之前,處于一致?tīng)顟B(tài)渠概。特別是在子類(lèi)集成Subject時(shí)容易發(fā)生茶凳,可用模板(Template)模式實(shí)現(xiàn);
// 存在狀態(tài)不一致的錯(cuò)誤代碼
public class ConcreteSubject : Subject
{
...
public override void SetState(object state)
{
base.Notify(); // 提前觸發(fā)播揪,導(dǎo)致?tīng)顟B(tài)不一致贮喧。
this.state = state;
}
}
更改為模板實(shí)現(xiàn)方式:
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
{
...
// 子類(lèi)只需要實(shí)現(xiàn)個(gè)性化的狀態(tài)處理
protected override void InternalSetState(object state)
{
...
this.state = state;
}
}
- 可以擴(kuò)展目標(biāo)的注冊(cè)接口,讓各觀察者注冊(cè)為僅對(duì)特定事件感興趣猪狈,以提高更新的效率塞淹。
public void Attach(Observer observer, InterestType interest);
示例
模擬兩個(gè)不同類(lèi)型的圖形控件罪裹,分別顯示當(dāng)前的時(shí)間饱普。
實(shí)現(xiàn)(C#)
示例結(jié)構(gòu)圖
using System;
using System.Threading;
using System.Collections.Generic;
// 目標(biāo)主題基類(lèi)
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);
}
// 具體的目標(biāo)主題逮诲,以3秒間隔發(fā)出通知
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;}
}
// 模擬時(shí)鐘控件1
public class DigitalClock : Observer
{
private readonly ClockTimer subject;
public DigitalClock(ClockTimer subject)
{
this.subject = subject;
this.subject.Attach(this); // 注冊(cè)監(jiān)聽(tīng)
}
public override void Update(Subject theChangedSubject)
{
// 確認(rèn)是否為目標(biāo)監(jiān)聽(tīng)對(duì)象
if(this.subject == theChangedSubject)
{
Console.WriteLine("1.DigitalClock : " + this.subject.Now);
}
}
}
// 模擬時(shí)鐘控件2
public class AnalogClock : Observer
{
private readonly ClockTimer subject;
public AnalogClock(ClockTimer subject)
{
this.subject = subject;
this.subject.Attach(this); // 注冊(cè)監(jiān)聽(tīng)
}
public override void Update(Subject theChangedSubject)
{
// 確認(rèn)是否為目標(biāo)監(jiān)聽(tīng)對(duì)象
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();
}
}
// 控制臺(tái)輸出:
// 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