以生活故事引入觀察者模式核心思想,從經典 GoF 實作演進到 .NET 原生 事件-委派 機制,展現設計模式解決「解耦」與「封裝變化」的本質。
小雪(被觀察者)有多位追求者(觀察者),隊伍動態變動。傳統模式:追求者不斷輪詢詢問位置,小雪不斷回應,雙方精力耗盡。Nokia 方案:手機內建訂閱清單,位置變化時自動發送簡訊通知所有訂閱者。雙方恢復正常生活,唯有狀態變化時才通訊。
定義物件間一對多依賴,當一物件狀態改變,所有依賴物件自動收到通知並有機會回應。執行期可動態增減觀察者。
public interface IBoy {
void Show(string address); // 統一通知協定
}
public class BoyA : IBoy {
public void Show(string address) { Console.WriteLine("A:" + address); /* 假設轉韓文 */ }
}
public class BoyB : IBoy {
public void Show(string address) { Console.WriteLine("B:" + address); /* 假設轉英文 */ }
}
public class GPRSMobile {
private List<IBoy> boys = new List<IBoy>();
private string address = "";
public void AddBoy(IBoy b) { boys.Add(b); }
public void RemoveBoy(IBoy b) { boys.Remove(b); }
private void Notify(string address) {
for (int i = 0; i < boys.Count; i++) boys[i].Show(address);
}
public void OnAddressChanged(string newAddress) { Notify(newAddress); }
}
List<IBoy>)IBoy)投資人訂閱關注股票,價格變動時簡訊推送,無需守著交易大廳或電腦,正常工作生活不受干擾。
.NET 以 委派簽名 取代介面,事件 封裝發布/訂閱機制,型別安全 (委派含參數簽名,避免 C 風格函數指標的執行期錯誤)。
傳統寫法 (高耦合):迴圈與處理邏輯混雜,未知處理方式難擴充。
for (int i = 0; i <= 100; i++) if (i == input) { Console.WriteLine(i); MessageBox.Show(i.ToString()); /* 其他處理 */ }
.NET 事件-委派寫法 (低耦合):
public delegate void NumberEventHandler(object sender, NumberEventArgs e);
public class NumberEventArgs : EventArgs {
public int Number { get; set; }
public NumberEventArgs(int number) { Number = number; }
}
public class Subject {
public event NumberEventHandler NumberReached;
public void DoWithLoop(int number) {
for (int i = 0; i <= 100; i++)
if (i == number) { OnNumberReached(new NumberEventArgs(i)); }
}
protected virtual void OnNumberReached(NumberEventArgs e) {
if (NumberReached != null) NumberReached(this, e);
}
}
public class MainProgram {
public static void Main() {
Subject s = new Subject();
s.NumberReached += new NumberEventHandler(msgbox_NumberReached); // 靜態方法
MainProgram mp = new MainProgram();
s.NumberReached += new NumberEventHandler(mp.console_NumberReached); // 實例方法
s.DoWithLoop(input);
}
void console_NumberReached(object sender, NumberEventArgs e) { Console.WriteLine(e.Number); }
static void msgbox_NumberReached(object sender, NumberEventArgs e) { MessageBox.Show(e.Number.ToString()); }
}
關鍵差異:觸發點 (OnNumberReached) 與處理邏輯 (console_NumberReached, msgbox_NumberReached) 完全分離,迴圈不知有多少處理者,擴充零修改觸發端。
設計模式核心在解決解耦與封裝變化,服務於可維護性、可擴充性、靈活性。學習重點不在背誦原型,而在理解適用場景與目的,內化為自身設計直覺。
「學習數學非背誦定理,而是循歷史理解前人如何分析解決問題,學到『漁』而非只求『魚』」
另一視角:初學者雖知原則卻不知如何落地專案,或需大量實踐 (量變) 促成質變,先不論過度設計,寫到一定程度自然領悟精髓。