文章

觀察者模式 - Observer Pattern

用途

管理相依的觀察者,在被觀察對象發生變動發出通知觀察者

classDiagram
  note for Client "Create and register Observers, notify all observers when you need."
  Client ..> IObserver
  Client ..> ISubject
  IObserver <|.. ConcreteObserverA
  IObserver <|.. ConcreteObserverB
  ISubject <|.. ConcreteSubject
  IObserver --o ISubject
  namespace Newpaper {
    class IObserver{
      <<Interface>>
      +Update()
    }
    class ISubject{
      <<Interface>>
      +Register(IObserver observer)
      +Unregister(IObserver observer)
      +Notify()
    }
    class ConcreteObserverA{
      +Update()
    }
    class ConcreteObserverB{
      +Update()
    }
    class ConcreteSubject{
      -IObserver[] observers
      +Register(IObserver observer)
      +Unregister(IObserver observer)
      +Notify()
    }
  }
  class Client{
    +Operation()
  }

例子

觀察者模式(Observer Pattern)是一種行為型設計模式,它定義了一種一對多的依賴關係,讓一個物件的狀態改變時,所有依賴它的物件都會得到通知並自動更新
這種模式常被用於實現分布式事件處理系統

在報社和訂閱者的例子中,可以將報社視為被觀察者(Subject),而訂閱者則是觀察者(Observer)
當報社發佈新的報紙時,所有訂閱者都應該得到通知,以便他們可以取得最新的新聞

以下是觀察者模式在報社和訂閱者情境中的概念解釋:

被觀察者(Subject,報社):

報社是被觀察者,它維護一個訂閱者列表,追蹤所有訂閱它的人或系統
當報社有新的報紙發佈時,它會通知所有訂閱者,告訴他們有新的資訊可用

觀察者(Observer,訂閱者):

訂閱者是觀察者,它們註冊在報社上,表示他們有興趣接收最新的新聞
每個訂閱者實現一個更新方法,當被觀察者通知時,這個方法被呼叫,以便訂閱者可以更新自己的狀態,例如取得最新的報紙內容

通知機制:

當報社有新的報紙時,它遍歷訂閱者列表,呼叫每個訂閱者的更新方法,通知它們有新的資訊可用
訂閱者收到通知後,執行相應的操作,例如讀取最新報紙

松耦合(Loose Coupling):

觀察者模式實現了松耦合,報社和訂閱者之間的關係是鬆散的
報社不需要知道訂閱者的詳細實現,只需知道它們有一個更新方法即可
新的訂閱者可以輕鬆加入,不會影響報社,反之亦然
例子:

報社發佈新報紙,所有訂閱者(讀者)都能及時獲得通知並閱讀最新的內容
每個訂閱者可以獨立地處理通知,不影響其他訂閱者
總的來說,觀察者模式提供了一種有效的方法,使物件之間能夠動態地建立關係,並保持低耦合度,同時允許一對多的通信
在報社和訂閱者的例子中,這種模式使得報社和訂閱者之間的關係更靈活,並且方便了新的訂閱者的加入

Observer

1
2
3
4
5
// 訂閱者(讀者)
public interface IObserver
{
    void Update(string news);  // 更新訊息
}

Concrete Observer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 讀者
public class Reader : IObserver
{
    public string Name { get; }

    public Reader(string name)
    {
        Name = name;
    }

    public void Update(string news)
    {
        Console.WriteLine($"Reader {Name} received news: {news}");
    }
}

Subject

1
2
3
4
5
6
7
8
9
10
// 主題(報社)
public interface ISubject
{
    // 註冊訂閱者
    void Register(IObserver observer);
    // 取消註冊訂閱者
    void Unregister(IObserver observer);
    // 通知訂閱者
    void Notify();
}

Concrete Subject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 報社
public class Newspaper : ISubject
{
    private List<IObserver> observers;
    private string latestNews;

    public Newspaper()
    {
        observers = new List<IObserver>();
    }

    public void Register(IObserver observer)
    {
        observers.Add(observer);
    }

    public void Unregister(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in observers)
        {
            observer.Update(latestNews);
        }
    }

    public void PublishNews(string news)
    {
        latestNews = news;
        Notify();
    }
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Newspaper newspaper = new Newspaper();
Reader reader1 = new Reader("John");
Reader reader2 = new Reader("Alice");

newspaper.Register(reader1);
newspaper.Register(reader2);

newspaper.PublishNews("Breaking News: COVID-19 vaccine approved!");

newspaper.Unregister(reader2);

newspaper.PublishNews("Sports: Home team wins the championship!");

// 輸出:
// Reader John received news: Breaking News: COVID-19 vaccine approved!
// Reader Alice received news: Breaking News: COVID-19 vaccine approved!
// Reader John received news: Sports: Home team wins the championship!

延伸

  • RxJS: Observable
本文章以 CC BY 4.0 授權