觀察者模式——定義了對象之間的一對多依賴,這樣一來,當一個對像改變狀态時,它的所有依賴者都會收到通知并自動更新.
從定義可以看出,OBSERVER(觀察者)模式邏輯上需要兩組對象來實作.首先它必需要有釋出者(Publish),也可稱為被觀察的目标 (Subject)(習慣上都稱它為目标Subject,後面我們都稱它作目标Subject),另外就是訂閱者(Subscribe),習慣上稱為觀察 者(Observer).一個目标對象對應多個觀察者對象,目标對象發生變化時,所有在目标對象中注冊的觀察者對象會得到通知,自動更新自己.
觀察者模式UML圖如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-YWan5yMmhDN1U2MygzNkhDMiZ2MhBjY1MDOiBzYyEDM2M2N18CXzIzLcdDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL1M3Lc9CX6MHc0RHaiojIsJye.gif)
觀察者模式的相關角色:
1、抽象主體(Subject)角色:也 就是被關注的對象,是一對多關系中的那個“一”。它的相關資訊的變化将會通知給訂閱這個變化的觀察者。主體角色把所有對觀察考對象的引用儲存在一個集合 (List,ArrayList.....)裡,每個主體可能管理若幹數量的觀察者。抽象主體提供一個接口,可以增加和删除觀察者對象,主體角色又叫做抽 象被觀察者(Observable)角色,一般用一個抽象類或者一個接口實作。
2、抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主體的通知時更新自己。這個接口叫做更新接口(Update)。抽象觀察者角色一般用一個抽象類或者一個接口實作。在這個示意性的實作中,更新接口隻包含一個方法(即Update()方法),這個方法叫做更新方法。
3、具體主體(ConcreteSubject)角色:将有關狀态存入具展現察者對象;在具體主體的内部狀态改變時,給所有登記過的觀察者發出通知。具體主體角色又叫做具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實作。
4、具體觀察者(ConcreteObserver)角色:存儲與主體的狀态自恰的狀态。具展現察者角色實作抽象觀察者角色所要求的更新接口,以便使本身的狀态與主體的狀态相協調。如果需要,具展現察者角色可以儲存一個指向具體主體對象的引用。具體觀察者角色通常用一個具體子類實作。
下面,我們用代碼來示例觀察者模式。
程式如下圖:
一、觀察者模式的基本思路
1、抽象主體Subject
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace MyObserver
{
//定義Subject抽象類,它是'ConcreteSubject'具體目标對象的基類
//要實作Observer模式時,通常将資料對象作為目标(Subject),各個顯示資料的對象作為觀察者Observer
//每一個觀察者(Observer)通過調用目标(Subject)中的一個公有(public)方法,在他所感興趣的資料中注冊(registers)自己。
//這樣,當資料改變時,每一個目标(Subject)通過觀察者(Observer)的接口發送更新通知。
abstract class Subject
{
#region 定義一個List來裝盛所有與此資料對象相聯系的觀察者Observer
private List<Observer> _observers = new List<Observer>();
#endregion
#region 附加或解除Observer功能(注冊或登出功能)
public void Attach(Observer observer)
{
_observers.Add(observer); //observer觀察者在此資料對象中注冊(registers)自己。
}
public void Detach(Observer observer)
_observers.Remove(observer);//在此資料對象中取消注冊,也即讓對象變更時不用再通知此observer觀察者
#region 通知在_observers清單中的所有觀察者
public void Nofity()
//周遊觀察者清單,按清單的名錄逐一通知
foreach (Observer o in _observers)
{
o.Update();
}
}
}
2、具體主體ConcreteSubject
//定義具體資料對象類,它繼承自Subject抽象類
class ConcreteSubject:Subject
{
#region SubjectState屬性
private string _subjectState;
public string SubjectState
get { return _subjectState; }
set { _subjectState = value; }
3、抽象觀察者Observer
#region 定義Observer抽象類,它是ConcreteObserver的基類
abstract class Observer
//定義一個用于發送更新通知的接口
//這樣,當資料改變時,每一個目标(Subject)通過觀察者(Observer)的接口發送更新通知。
public abstract void Update();
#endregion
4、具體觀察者ConcreteObserver
class ConcreteObserver:Observer
private string _name;
private string _observerState;
private ConcreteSubject _subject;
public ConcreteSubject Subject
get { return _subject; }
set { _subject = value; }
#region 構造函數
public ConcreteObserver(ConcreteSubject subject, string name)
this._subject = subject;
this._name = name;
#region 實作目标資料更新通知接口
public override void Update()
_observerState = _subject.SubjectState;
Console.WriteLine("觀察者 {0} 收到的資料對象的新狀态值是 {1}",_name,_observerState);
5、用戶端代碼
#region 基本思路示例
Console.WriteLine("----------觀察者模式基本思路示例--------");
ConcreteSubject s = new ConcreteSubject(); //首先建立一個資料對象
s.Attach(new ConcreteObserver(s, "X")); //向這個資料對象内注冊三個觀察者X,Y,Z
s.Attach(new ConcreteObserver(s, "Y"));
s.Attach(new ConcreteObserver(s, "Z"));
s.SubjectState = "ABC"; //改變資料對象的狀态值
s.Nofity(); //調用資料對象的通知功能來依次通知已經注冊的觀察者
Console.ReadKey();
#endregion
二、我的團長我的團使用觀察者模式
這裡,我們讓孟煩了在前哨望風,當他發現敵情時,他馬上通知所有兄弟們準備戰鬥。這裡,孟煩了就是具體主體ConcreteSubject,他的那些兄弟:要麻,迷龍,豆餅....等等都是具體觀察者(ConcreteObserver)
1、抽象主體Subject:Guard
abstract class Guard
#region 定義一個List來收集所有需要通知到的其它士兵
private List<Soldier> _soldiers = new List<Soldier>();
public void Attach(Soldier observer)
_soldiers.Add(observer); //observer觀察者在此資料對象中注冊(registers)自己。
public void Detach(Soldier observer)
_soldiers.Remove(observer);//在此資料對象中取消注冊,也即讓對象變更時不用再通知此observer觀察者
foreach (Soldier o in _soldiers)
2、具體主體ConcreteSubject:ConcreteGurad
class ConcreteGurad:Guard
public string Name
get { return _name; }
set { _name = value; }
#region FightInfo屬性
private string _fightInfo;
public string FightInfo
get { return _fightInfo; }
set { _fightInfo = value; }
public ConcreteGurad(string name)
3、抽象觀察者Observer:Soldier
abstract class Soldier
4、具體觀察者ConcreteObserver:ConcreteSoldier
class ConcreteSoldier:Soldier
private ConcreteGurad _subject;
public ConcreteGurad Subject
public ConcreteSoldier(ConcreteGurad subject, string name)
_observerState = _subject.FightInfo ;
Console.WriteLine("士兵 '{0}' 收到哨兵 '{1}' 的信号:{2}",_name,_subject.Name ,_observerState);
5、用戶端代碼
#region 我的團長我的團
Console.WriteLine("----------我的團長我的團觀察者模式示例--------");
ConcreteGurad cg = new ConcreteGurad("孟煩了");
ConcreteSoldier yaoma = new ConcreteSoldier(cg, "要麻");
ConcreteSoldier sepigu = new ConcreteSoldier(cg, "蛇屁股");
ConcreteSoldier doubing = new ConcreteSoldier(cg, "豆餅");
ConcreteSoldier kangya = new ConcreteSoldier(cg, "康丫");
ConcreteSoldier milong = new ConcreteSoldier(cg, "迷龍");
ConcreteSoldier bula = new ConcreteSoldier(cg, "不辣");
cg.Attach(yaoma);
cg.Attach(sepigu);
cg.Attach(doubing);
cg.Attach(kangya);
cg.Attach(milong);
cg.Attach(bula);
cg.FightInfo = "鬼子從右邊摸上來了,大家準備殲滅他們
.";
cg.Nofity();
程式運作後效果如下:
總結:
1、要點
(1)抽象主體角色公開了自身的事件,可以給任意觀察者訂閱。
(2)象觀察者角色定義了統一的處理行為,在C#中使用事件-代理模式的話,統一的處理行為并不這麼重要,有的時候甚至還會限制靈活性。
(3)觀察者往往隻需要實作響應方法即可。
(4)有多個主體角色、多個觀察者角色交錯,也可以一個類型是兩個角色,主體也可以提供多個事件。從應用上來說觀察者模式變化是非常多的。
2、優缺點
觀察者模式的優缺點
Observer模式的優點是實作了表示層和資料邏輯層的分離,并定義了穩定的更新消息傳遞機制,類别清晰,并抽象了更新接口,使得可以有各種各樣不同的表示層(觀察者)。
但是其缺點是每個外觀對象必須繼承這個抽像出來的接口類,這樣就造成了一些不友善,比如有一個别人寫的外觀對象,并沒有繼承該抽象類,或者接口不對,我們 又希望不修改該類直接使用它。雖然可以再應用Adapter模式來一定程度上解決這個問題,但是會造成更加複雜煩瑣的設計,增加出錯幾率。
觀察者模式的效果有以下幾個優點:
(1)觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的隻是一個具展現察者聚集,每一個具展現察者都符合一個抽象觀察者的接 口。被觀察者并不認識任何一個具體觀察者,它隻知道它們都有一個共同的接口。由于被觀察者和觀察者沒有緊密地耦合在一起,是以它們可以屬于不同的抽象化層 次。
(2)觀察者模式支援廣播通信。被觀察者會向所有的登記過的觀察者發出通知。
觀察者模式有下面的一些缺點:
(1)如果一個被觀察者對象有很多直接和間接的觀察者的話,将所有的觀察者都通知到會花費很多時間。
(2)如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導緻系統崩潰。在使用觀察考模式時要特别注意這一點。
(3)如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統必須保證投遞是以自恰的方式進行的。
(4)雖然觀察者模式可以随時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎麼發生變化的。