出處:https://www.cnblogs.com/wyy1234/
閱讀目錄
1.觀察者模式介紹
觀察者模式又叫發布-訂閱模式,它定義了對象間的一種一對多關系,當一個對象的狀態發生改變時,所有依賴於它的對象都會收到通知並被自動更新。觀察者模式就四個角色:抽象主題,具體主題,抽象觀察者,具體觀察者。抽象主題是一個抽象的接口或者抽象類,對主題的功能進行抽象,抽象觀察者對具體的觀察者進行抽象。觀察者模式在軟件開發中的應用十分廣泛,如微信訂閱號、微博訂閱等都采用了觀察者模式,我們關注了某個明星的微博,當這個明星更新微博狀態時我們的微博都會收到通知,明星的微博就是主題角色,我們的微博屬於觀察者角色。
下邊通過大話設計模式中秘書做卧底的例子來理解觀察者模式的用法。上班時間有的同事喜歡偷偷看股票行情,有的同事看NBA直播,但是老板會不定時來辦公室,如果被老板逮到就不好了,於是大家想出來一個辦法:讓秘書小妹在外邊把風,如果老板來了就通知下大家,這樣就不會被逮到了。這個栗子中秘書小妹就是一個主題,同事們屬於觀察者,秘書小妹如果看到老板過來就會通知 送她零食的同事們。代碼比較簡單:
//抽象主題類
public interface ISubject
{
//添加觀察者 送零食的加進來,老板來了通知你
void Add(Observer observer);
//刪除觀察者 不送零食的秘書小妹就不通知了
void Remove(Observer observer);
//主題狀態
string SubjectState { get; set; }
//通知方法
void Notify();
}
//具體主題 ,秘書類
public class Mishu : ISubject
{
//秘書要知道通知哪些同事
private IList<Observer> observers = new List<Observer>();
public void Add(Observer observer)
{
observers.Add(observer);
}
public void Remove(Observer observer)
{
observers.Remove(observer);
}
public string SubjectState { get; set; }
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}
//抽象觀察者
public abstract class Observer
{
//名字
protected string name;
//觀察者要知道自己訂閱了那個主題
protected ISubject sub;
public Observer(string name, ISubject sub)
{
this.name = name;
this.sub = sub;
}
//接受到通知后的更新方法
public abstract void Update();
}
//看股票的同事
public class StockObserver : Observer
{
public StockObserver(string name, ISubject sub) : base(name, sub) { }
public override void Update()
{
Console.WriteLine($"通知內容:{sub.SubjectState},反應:{name}關閉股票行情,繼續工作!");
}
}
//看NBA的同事
public class NBAObserver : Observer
{
public NBAObserver(string name, ISubject sub) : base(name, sub) { }
public override void Update()
{
Console.WriteLine($"通知內容:{sub.SubjectState},反應:{name}關閉NBA直播,繼續工作!");
}
}
/// <summary>
/// 客戶端調用
/// </summary>
class Program
{
static void Main(string[] args)
{
Mishu mishu = new Mishu();
//新建同事 觀察者角色
Observer tongshi1 = new StockObserver("巴菲特", mishu);
Observer tongshi2 = new NBAObserver("麥迪", mishu);
//秘書小妹要知道哪些同事要通知(主題要知道所有訂閱了自己的觀察者)
mishu.Add(tongshi1);
mishu.Add(tongshi2);
//主題狀態更改了
mishu.SubjectState = "老板回來了!";
//調用主題的通知方法
mishu.Notify();
Console.ReadKey();
}
}
運行程序,執行結果如下:

上邊的例子中秘書小妹充當主題角色,上班偷懶的同事充當觀察者角色,通過觀察者模式實現了功能。但是這里還有一個小問題:上邊例子能夠成功的前提是所有觀察者的角色都有一個Update()方法來執行更新。但是有時候各個觀察者並不都是相同的類型,如觀察者1收到通知執行Update1()方法,而觀察者2收到通知執行的是Update2()方法,這時候采用上邊的模式就不能滿足需求了。怎么改進呢?①各個觀察者不屬於同一類,所以不需要抽象觀察者類了 ②因為各個觀察者的反應不是同一的Update(),所以我們不能foreach遍歷觀察者集合來統一調用Update()方法了,這時可以考慮通過事件委托在客戶端確定所有觀察者的反應。改進后的代碼如下:
//抽象主題角色
public interface ISubject
{
//添加觀察者
void Add(Observer observer);
//刪除觀察者
void Remove(Observer observer);
//主題狀態
string SubjectState { get; set; }
//通知方法
void Notify();
}
//***********************1.定義一個委托
public delegate void EventHandler();
//具體主題角色 秘書類
public class Mishu : ISubject
{
public event EventHandler Update;
//存儲要通知的同事
public IList<Observer> observers = new List<Observer>();
public string SubjectState { get; set; }
public void Add(Observer observer)
{
observers.Add(observer);
}
public void Remove(Observer observer)
{
observers.Remove(observer);
}
//**********2.通知方法中不能通過遍歷來統一調用每個觀察者的Update()方法了,改成執行一個委托
public void Notify()
{
Update();
}
}
//抽象觀察者角色
public abstract class Observer {
protected ISubject sub;
protected string name;
protected Observer(string name,ISubject sub)
{
this.name = name;
this.sub = sub;
}
}
//具體觀察者角色 看股票的同事
public class StockObserver
{
public string name;
public ISubject sub;
public StockObserver(string name,ISubject sub)
{
this.name = name;
this.sub = sub;
}
public void CloseStockMarket()
{
Console.WriteLine($"通知內容:{sub.SubjectState},反應:{name}關閉股票行情,繼續工作!");
}
}
//具體觀察者角色 看NBA的同事
public class NBAObserver
{
public string name;
public ISubject sub;
public NBAObserver(string name, ISubject sub)
{
this.name = name;
this.sub = sub;
}
public void CloseNBA()
{
Console.WriteLine($"通知內容:{sub.SubjectState},反應:{name}關閉NBA直播,繼續工作!");
}
}
class Program
{
static void Main(string[] args)
{
Mishu mishu = new Mishu();
//觀察者訂閱了主題mishu
StockObserver tongshi1 = new StockObserver("巴菲特", mishu);
NBAObserver tongshi2 = new NBAObserver("麥迪", mishu);
//*******************3.將遍歷觀察者並調用觀察者的Update(),改成了事件委托形式
mishu.Update += tongshi1.CloseStockMarket;
mishu.Update += tongshi2.CloseNBA;
//主題狀態更改,並通知
mishu.SubjectState = "老板回來了!";
mishu.Notify();
Console.ReadKey();
}
}
運行結果:

我們看到通過事件委托我們可以實現讓不同的觀察者調用不同的方法,當我們遇到類似這樣的情況:點擊一個按鈕,有的頁面元素顯示Show(),有的隱藏Hide(),有的關閉Close()就可以采用這種模式。但是這種模式沒有對具體的觀察者進行抽象,如果觀察者太多也會造成事件委托過於復雜。兩種觀察者模式的實現各有利弊,我們可以根據實際的情況來選擇。
2.小結
上邊栗子的類圖:

觀察者模式的使用場景:當一個對象的狀態發生變化時,需要讓其它對象知道並作出反應可以考慮觀察者模式。
觀察者模式的優點:想一下如果不使用觀察者模式,訂閱者怎么獲取訂閱號的更新?最直接的方法應該就是輪詢了,這種方式十分浪費資源,而且獲取更新也不及時。觀察者模式的主要功能就是解決了這一問題。這種模式符合依賴倒置原則,同時降低了觀察者和主題間的耦合,建立了一套觸發機制。

