Observer Pattern(觀察者模式)

是什麼?

Observer Pattern 定義物件之間的一對多依賴關係,當被觀察的物件(Subject)狀態發生改變時,所有依賴它的觀察者(Observer)都會自動收到通知並更新。

ℹ️GoF 分類

Observer 屬於行為型模式(Behavioral Pattern),重點在於物件之間如何建立動態的通知機制。

什麼時候用?

用以下 if/then 條件判斷:

什麼時候不該用?

⚠️過度設計警告

如果通知對象固定且數量少(例如只有 1-2 個),直接呼叫方法更簡單。Observer 會引入額外的間接層,且偵錯時通知鏈可能難以追蹤。當觀察者之間有執行順序依賴時,Observer Pattern 也不適合。

執行流程

1

定義 Subject 介面

提供 Subscribe、Unsubscribe、Notify 方法

2

定義 Observer 介面

宣告 Update 方法供 Subject 呼叫

3

實作具體 Subject

維護觀察者清單,狀態變化時呼叫 Notify

4

實作具體 Observer

在 Update 中定義收到通知後的反應

5

註冊與通知

Observer 訂閱 Subject,Subject 狀態改變時通知所有 Observer

流程解讀:Observer 的核心是「訂閱制」。Subject 維護一份 Observer 清單,狀態改變時遍歷清單逐一呼叫 Update()。Observer 透過 Subscribe 加入清單、Unsubscribe 離開清單,全程動態管理。這種 Push Model(Subject 主動推送)是最常見的實作方式;另一種是 Pull Model(Subject 只通知「有變化」,Observer 自己來拉資料),適合 Observer 只需要部分資料的場景。

程式碼範例

C# 版本

csharp
// 1. 定義介面
public interface IObserver
{
    void Update(string videoTitle);
}
 
public interface IChannel
{
    void Subscribe(IObserver observer);
    void Unsubscribe(IObserver observer);
    void Notify();
}
 
// 2. 具體 Subject
public class YouTubeChannel : IChannel
{
    private readonly List<IObserver> _subscribers = new();
    private string _latestVideo = "";
 
    public string ChannelName { get; }
 
    public YouTubeChannel(string name) => ChannelName = name;
 
    public void Subscribe(IObserver observer) => _subscribers.Add(observer);
    public void Unsubscribe(IObserver observer) => _subscribers.Remove(observer);
 
    public void Notify()
    {
        foreach (var subscriber in _subscribers)
            subscriber.Update(_latestVideo);
    }
 
    public void PublishVideo(string title)
    {
        _latestVideo = title;
        Console.WriteLine($"[{ChannelName}] 發布新影片:{title}");
        Notify();
    }
}
 
// 3. 具體 Observer
public class Subscriber : IObserver
{
    public string Name { get; }
    public Subscriber(string name) => Name = name;
 
    public void Update(string videoTitle)
    {
        Console.WriteLine($"  {Name} 收到通知:新影片「{videoTitle}」已上線!");
    }
}
 
// 4. 使用
var channel = new YouTubeChannel("設計模式教學");
var alice = new Subscriber("Alice");
var bob = new Subscriber("Bob");
 
channel.Subscribe(alice);
channel.Subscribe(bob);
channel.PublishVideo("Observer Pattern 完全指南");
// Alice 和 Bob 都收到通知
 
channel.Unsubscribe(bob);
channel.PublishVideo("Strategy Pattern 快速入門");
// 只有 Alice 收到通知

TypeScript 版本

typescript
// 1. 定義介面
interface Observer {
  update(videoTitle: string): void;
}
 
interface Channel {
  subscribe(observer: Observer): void;
  unsubscribe(observer: Observer): void;
  notify(): void;
}
 
// 2. 具體 Subject
class YouTubeChannel implements Channel {
  private subscribers: Observer[] = [];
  private latestVideo = "";
 
  constructor(public channelName: string) {}
 
  subscribe(observer: Observer) { this.subscribers.push(observer); }
  unsubscribe(observer: Observer) {
    this.subscribers = this.subscribers.filter(s => s !== observer);
  }
 
  notify() {
    this.subscribers.forEach(s => s.update(this.latestVideo));
  }
 
  publishVideo(title: string) {
    this.latestVideo = title;
    console.log(`[${this.channelName}] 發布新影片:${title}`);
    this.notify();
  }
}
 
// 3. 具體 Observer
class Subscriber implements Observer {
  constructor(public name: string) {}
  update(videoTitle: string) {
    console.log(`  ${this.name} 收到通知:新影片「${videoTitle}」已上線!`);
  }
}
 
// 4. 使用
const channel = new YouTubeChannel("設計模式教學");
const alice = new Subscriber("Alice");
const bob = new Subscriber("Bob");
 
channel.subscribe(alice);
channel.subscribe(bob);
channel.publishVideo("Observer Pattern 完全指南");
 
channel.unsubscribe(bob);
channel.publishVideo("Strategy Pattern 快速入門");

Python 版本

python
from abc import ABC, abstractmethod
 
# 1. 定義介面
class Observer(ABC):
    @abstractmethod
    def update(self, video_title: str) -> None:
        pass
 
class Channel(ABC):
    @abstractmethod
    def subscribe(self, observer: Observer) -> None:
        pass
    @abstractmethod
    def unsubscribe(self, observer: Observer) -> None:
        pass
    @abstractmethod
    def notify(self) -> None:
        pass
 
# 2. 具體 Subject
class YouTubeChannel(Channel):
    def __init__(self, name: str):
        self.channel_name = name
        self._subscribers: list[Observer] = []
        self._latest_video = ""
 
    def subscribe(self, observer: Observer) -> None:
        self._subscribers.append(observer)
 
    def unsubscribe(self, observer: Observer) -> None:
        self._subscribers.remove(observer)
 
    def notify(self) -> None:
        for subscriber in self._subscribers:
            subscriber.update(self._latest_video)
 
    def publish_video(self, title: str) -> None:
        self._latest_video = title
        print(f"[{self.channel_name}] 發布新影片:{title}")
        self.notify()
 
# 3. 具體 Observer
class Subscriber(Observer):
    def __init__(self, name: str):
        self.name = name
 
    def update(self, video_title: str) -> None:
        print(f"  {self.name} 收到通知:新影片「{video_title}」已上線!")
 
# 4. 使用
channel = YouTubeChannel("設計模式教學")
alice = Subscriber("Alice")
bob = Subscriber("Bob")
 
channel.subscribe(alice)
channel.subscribe(bob)
channel.publish_video("Observer Pattern 完全指南")
 
channel.unsubscribe(bob)
channel.publish_video("Strategy Pattern 快速入門")

Java 版本

java
import java.util.ArrayList;
import java.util.List;
 
// 1. 定義介面
public interface Observer {
    void update(String videoTitle);
}
 
public interface Channel {
    void subscribe(Observer observer);
    void unsubscribe(Observer observer);
    void notifyObservers();
}
 
// 2. 具體 Subject
public class YouTubeChannel implements Channel {
    private final List<Observer> subscribers = new ArrayList<>();
    private String latestVideo = "";
    private final String channelName;
 
    public YouTubeChannel(String name) { this.channelName = name; }
 
    public void subscribe(Observer o) { subscribers.add(o); }
    public void unsubscribe(Observer o) { subscribers.remove(o); }
 
    public void notifyObservers() {
        for (Observer o : subscribers) o.update(latestVideo);
    }
 
    public void publishVideo(String title) {
        latestVideo = title;
        System.out.println("[" + channelName + "] 發布新影片:" + title);
        notifyObservers();
    }
}
 
// 3. 具體 Observer
public class Subscriber implements Observer {
    private final String name;
    public Subscriber(String name) { this.name = name; }
 
    public void update(String videoTitle) {
        System.out.println("  " + name + " 收到通知:新影片「" + videoTitle + "」已上線!");
    }
}
 
// 4. 使用
YouTubeChannel channel = new YouTubeChannel("設計模式教學");
Subscriber alice = new Subscriber("Alice");
Subscriber bob = new Subscriber("Bob");
 
channel.subscribe(alice);
channel.subscribe(bob);
channel.publishVideo("Observer Pattern 完全指南");
 
channel.unsubscribe(bob);
channel.publishVideo("Strategy Pattern 快速入門");

結構圖

Client
uses
Subject (IChannel)
notifies
ConcreteSubject (YouTubeChannel)
implements
Observer Interface
ConcreteObserver (Subscriber)

結構解讀:Subject 和 Observer 之間透過介面解耦。Subject 只知道 Observer Interface(不知道具體是誰),Observer 只知道 Subject Interface(不知道具體是哪個頻道)。這種鬆耦合讓兩邊可以獨立擴充 — 新增 Observer 類型或新增 Subject 類型都不影響對方。

實戰補充

💡資深開發者經驗

C# 原生支援:C# 的 event 關鍵字就是 Observer Pattern 的語言級實作,搭配 delegate 使用非常方便。大部分情況下不需要手動實作 Observer。

Reactive Programming:RxJS、Reactor、RxJava 把 Observer 推到極致。Observable 不只通知變化,還能做 filter、map、merge 等串流操作。

Event Bus 陷阱:全域 Event Bus 很方便,但容易變成隱形的全域狀態。當事件滿天飛時,偵錯會變成噩夢。建議限制 Event Bus 的作用範圍,或改用更明確的 Mediator。

記憶體洩漏:Observer 忘記取消訂閱是常見的 Bug。C# 的 WeakReference、React 的 useEffect cleanup、Angular 的 takeUntil 都是解法。

理解測驗

🤔 Observer Pattern 中,Subject 和 Observer 之間是什麼關係?

🤔 使用 Observer Pattern 最常見的 Bug 是什麼?

🤔 以下哪個不是 Observer Pattern 的實際應用?

面試常見問題

Q: Observer Pattern 的 Push Model 和 Pull Model 有什麼差別?

A: Push Model — Subject 在 Notify 時把完整資料推送給所有 Observer(如範例的 Update(videoTitle))。Pull Model — Subject 只通知「狀態變了」,Observer 再自己呼叫 Subject 的 getter 取需要的資料。Push 簡單但 Observer 可能收到不需要的資料;Pull 靈活但 Observer 需要知道 Subject 的介面。

Q: 如何避免 Observer 的記憶體洩漏問題?

A: 三個策略:(1) 生命週期管理 — 物件銷毀時務必 Unsubscribe(C# 的 IDisposable、React 的 useEffect cleanup);(2) 弱參考 — 用 WeakReference 讓 GC 能回收已不需要的 Observer;(3) 自動清理 — Subject 在 Notify 時偵測已失效的 Observer 並移除。

Q: C# 的 event 和手寫 Observer 有什麼差異?

A: C# 的 event 是語言層級的 Observer 實作。event 限制只有宣告者能 Invoke,外部只能 Subscribe/Unsubscribe,比手寫更安全。手寫 Observer 的優勢是可以自定義更複雜的邏輯(如篩選通知、非同步通知、優先順序)。

相關模式

| 模式 | 關係 | |------|------| | Mediator | Observer 是廣播(一對多),Mediator 是協調(多對多)。Observer 的 Subject 不管 Observer 怎麼反應,Mediator 會根據事件協調各方 | | Command | 可搭配使用 — Observer 收到通知後建立 Command 放入佇列,實現非同步處理 | | Strategy | Observer 的 Update 方法可以用 Strategy 來決定「收到通知後要做什麼」 | | Singleton | Event Bus(全域事件匯流排)常設計為 Singleton,但要小心變成隱形的全域狀態 |

重點整理

💡一句話記住

Observer Pattern = 報紙訂閱制:有新消息就自動派送,取消訂閱就不再收到。 口訣:「訂閱收通知,退訂斷連結」

| 概念 | 說明 | |------|------| | Subject(被觀察者) | 維護觀察者清單,狀態變化時發出通知 | | Observer(觀察者) | 定義接收通知的介面 | | Subscribe/Unsubscribe | 動態新增或移除觀察者 | | 核心好處 | 鬆耦合 — Subject 不需要知道 Observer 的具體型別 | | 代價 | 通知鏈難追蹤、忘記取消訂閱會記憶體洩漏 |

你可能也想看

Proxy PatternCommand Pattern

按 ← → 鍵切換課程