在 .NET 中,觀察者設計模式會實作為一組介面。 介面 System.IObservable<T> 代表數據提供者,該提供者也負責提供 IDisposable 可讓觀察者取消訂閱通知的實作。 介面 System.IObserver<T> 代表觀察者。
本文說明使用這些介面實作觀察者設計模式時應遵循的最佳做法。
在實施前請考慮替代方案
IObservable<T> 和 IObserver<T> 介面非常適合推播式通知情境,但其他 .NET 模式可能更適合。 若要在單一應用程式內簡單通知,請使用 事件。 對於由取用者控制步調的非同步提取式序列,請使用 IAsyncEnumerable<T>。 對於具有背壓的生產者-消費者模式,請使用 System.Threading.Channels。 對於複雜的事件組合、過濾與轉換,請使用 System.Reactive (Rx.NET) 套件,而非直接實作 IObservable<T>。 欲了解更多資訊,請參閱 觀察者設計模式。
通知資料請使用獨立的類型
包含提供者傳送給觀察者資料的物件對應於 和 IObservable<T>的IObserver<T>通用型別參數。 雖然這個物件可以和 IObservable<T> 實作相同,但請將其定義為獨立型別。 專用的資料型態將提供者的責任與通知載荷分開,並使 API 更容易演進。
不要只依賴通知順序
觀察員接收通知的順序並未明確定義。 提供方可自由以任何方式決定通知順序,因此請勿撰寫會依賴於比另一個觀察者先收到或後收到通知的觀察者。
使 Subscribe 與 Dispose 具備執行緒安全性
通常,提供者會 IObservable<T>.Subscribe 將觀察者加入由集合物件代表的訂閱者清單中,然後將該觀察者從該清單中移除來實作 IDisposable.Dispose 此方法。 觀察者可以隨時呼叫這些方法。 提供者/觀察者合約未明確指定在 IObserver<T>.OnCompleted 回呼方法之後由誰負責取消訂閱,因此提供者與觀察者都可能會嘗試從清單中移除同一個成員。
為避免競爭條件,請讓 Subscribe 和 Dispose 這兩個方法都具有執行緒安全性。 通常,這牽涉到使用 並行集合 或鎖定。 不具執行緒安全性的實作應明確註明其不具執行緒安全性。
記錄任何額外的合約保證
在提供者/觀察員合約之上加一層額外保證。 當你施加其他要求時,要明確指出,避免使用者對觀察員合約感到困惑。
將例外視為資訊
由於數據提供者與觀察者之間的鬆散結合,觀察者設計模式中的例外狀況是參考性的。 此特性影響提供者與觀察者如何處理例外。
只有在無法繼續更新時才呼叫 OnError。
方法 OnError 被設計為向觀察者傳遞訊息,這點與 IObserver<T>.OnNext 方法十分相似。 然而,該 OnNext 方法提供觀察者當前或更新的資料,而該 OnError 方法則表示提供者無法提供有效資料。
處理例外並呼叫 OnError 該方法時,請遵循以下最佳實務:
- 如果提供者有任何特定需求,則必須處理自己的例外狀況。
- 提供者不應期望或要求觀察者以特定方式處理例外。
- 提供者在處理會危害其提供更新能力的例外狀況時,應該呼叫 OnError 方法。 將此類例外資訊傳達給觀察者。 在其他情況下,則無需通知觀察員例外情況。
提供者呼叫 OnError or IObserver<T>.OnCompleted 方法後,不應有更多通知,提供者可以取消訂閱其觀察者。 然而,觀察員也可以隨時取消訂閱,包括在收到 OnError OR IObserver<T>.OnCompleted 通知前後。 觀察者的設計模式並不決定是提供者還是觀察者負責取消訂閱,因此雙方都可能嘗試取消訂閱。 通常,當觀察者取消訂閱時,這些資料會從訂閱者的收藏中移除。 在單執行緒應用程式中, IDisposable.Dispose 實作應確保物件參考有效且該物件是訂閱者集合的成員,然後才會嘗試移除該物件。 在多執行緒應用程式中,使用鎖來保護觀察者集合。
在觀察者中,將 OnError 通知視為資訊通知
當觀察者收到提供者的錯誤通知時,觀察者應將該例外視為資訊性,不應被要求採取任何特定行動。
當您回應 OnError 提供者的方法呼叫時,請遵循以下最佳實務:
- 不要從介面實作中拋出例外,例如 OnNext 或 OnError。 如果觀察者拋出例外,預期這些例外不會被處理。
- 為了保留呼叫堆疊,若觀察者想要拋出傳遞給其 OnError 方法的 Exception 物件,應先包裝該例外,再將其拋出。 為此目的使用標準例外物件。
不要用訂閱方式取消註冊
不要嘗試在 IObservable<T>.Subscribe 方法中取消註冊,因為可能會導致引用為空。
將觀察者附加於單一提供者
雖然你可以將觀察者附加給多個提供者,但建議的模式是只將一個 IObserver<T> 實例附加到一個 IObservable<T> 實例。