Observer Pattern(觀察者模式)
是什麼?
Observer Pattern 定義物件之間的一對多依賴關係,當被觀察的物件(Subject)狀態發生改變時,所有依賴它的觀察者(Observer)都會自動收到通知並更新。
ℹ️GoF 分類
Observer 屬於行為型模式(Behavioral Pattern),重點在於物件之間如何建立動態的通知機制。
什麼時候用?
用以下 if/then 條件判斷:
- 如果一個物件狀態改變時,需要通知的對象數量不固定(執行時動態增減)→ 用 Observer
- 如果通知對象的種類多樣(Email、Log、UI 更新),且未來會新增 → 用 Observer
- 如果你正在建立事件驅動架構(Event-Driven Architecture,指系統元件透過事件而非直接呼叫來溝通的架構)→ 用 Observer
- 如果通知對象固定只有 1-2 個且不會變 → 直接呼叫方法更簡單,不需要 Observer
什麼時候不該用?
⚠️過度設計警告
如果通知對象固定且數量少(例如只有 1-2 個),直接呼叫方法更簡單。Observer 會引入額外的間接層,且偵錯時通知鏈可能難以追蹤。當觀察者之間有執行順序依賴時,Observer Pattern 也不適合。
執行流程
定義 Subject 介面
提供 Subscribe、Unsubscribe、Notify 方法
定義 Observer 介面
宣告 Update 方法供 Subject 呼叫
實作具體 Subject
維護觀察者清單,狀態變化時呼叫 Notify
實作具體 Observer
在 Update 中定義收到通知後的反應
註冊與通知
Observer 訂閱 Subject,Subject 狀態改變時通知所有 Observer
流程解讀:Observer 的核心是「訂閱制」。Subject 維護一份 Observer 清單,狀態改變時遍歷清單逐一呼叫 Update()。Observer 透過 Subscribe 加入清單、Unsubscribe 離開清單,全程動態管理。這種 Push Model(Subject 主動推送)是最常見的實作方式;另一種是 Pull Model(Subject 只通知「有變化」,Observer 自己來拉資料),適合 Observer 只需要部分資料的場景。
程式碼範例
C# 版本
// 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 版本
// 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 版本
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 版本
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 快速入門");結構圖
結構解讀: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 的具體型別 | | 代價 | 通知鏈難追蹤、忘記取消訂閱會記憶體洩漏 |