---
tags: 設計模式
---
# 觀察者模式(Observer Pattern)
## 前言
- 當有複數個物件需要依據另外一個物件的狀態變化(相同的資料)執行對應的動作時, 就可以考慮使用觀察者模式
- 別稱 Publish-Subscribe Pattern
#### 問題需求
你需要設計一套軟體版本自動升級工具. 在區域網路內只能有一台電腦(命名為 Server)可以聯外網, 其他電腦(命名為 Client) 會透過 Server 得知是否有新的軟體版本可以升級. --- 通常是工廠環境, 為了安全等因素, 所以只允許一台設備對外.
為了解決這個問題 , 可能有兩種思路.
- Client 定時詢問 Server 是否有新的軟體版本
- Client 大部分的詢問都是無意義 ---> 浪費時間/資源去問
- 當 Client 數量多時, Server 也需要處理較多的 Query, 對 Server 可能是一種負擔.
- 當有新的軟體版本時, Server 會 Brocast 訊息出去.
(區網 IP Adress 全部送一遍 e.g. 192.168.0.0 ~ 192.168.255.255)
- Server 傳送的訊息大多沒有意義, 因為並不是所有 IP 位址都有 Client 在使用.
---> Server 浪費資源試圖通知無 Client 使用地 IP 位址.
##### 把思路2 擴展一下, 如果 Server 知道有哪些 Client (且知道他們的 IP Address), 是不是就可以再有新版本時, 才準確地通知這些 Client 呢 ?!
## Observer Pattern 介紹
### 定義
> Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
> Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
> * Applicability
> * Need to change other objects when one object changes state, but the other objects aren't necessarily known to the one object
> * Need to avoid tightly coupling an object with its observers
> * Consequences
> * Abstract coupling between Subject and Observer
> * Support for broadcast communciation
> * Unexpected updates can be a significant performance hit
- 觀察者模式會需要定義一個**一對多的物件依賴關係**, 當某個條件滿足時 (**通常是物件狀態改變**) 就通知其他相依物件執行某種動作
- 觀察者模式會將獲取資料的邏輯獨立抽出來, 並在資料狀態改變時通知對此變化有興趣的物件, 且這些物件**可在任何時間自由決定是否要繼續得到通知**.
- 觀察者模式擁有兩個角色 Subject(Publisher) & Observer(Subscriber)
- Subject --- 也被稱為 Observable or Publisher
- **其擁有某種資訊(其他 Observer 會感興趣其變化). 因為其會發布變化的訊息 (可能是自身狀態的改變, 也可能是滿足某種條件的變化)給其他的 Observer , 所以也被稱為發布者(Publisher)**.
- 要被觀察的對象
- Observer --- 也被稱為 Subscriber or Listener
- **需要依據 Subject 狀態做出反應**
- 任何對於 Subject (Publisher) 的狀態改變有興趣的實體
- 我猜測會使用 Observer 這個詞是因為其對 Subject 的狀態變化有興趣, 所以會想要觀察 Subject, 所以它是一個**觀察者(Observer)**
- 我猜測會使用 Subscriber 這個詞是因為其訂閱 Subject 的狀態變化, 所以它是一個**訂閱者(Subscriber)**
### UML Illustrate
```mermaid
%%{init:{
'theme': 'base', 'themeVariables':
{ 'fontSize': '19px', 'fontFamily': 'Inter'}
}
}%%
graph TD;
title[Subject notifies Observes when Something happen]
title-->Subject
style title fill:#FFF,stroke:#FFF
linkStyle 0 stroke:#FFF,stroke-width:0;
Subject -- Notify --> Observe1
Subject -- Notify --> Observe2
Subject -- Notify --> Observe3
Subject -- Notify --> ...
subgraph Observers
Observe1
Observe2
Observe3
...
end
```


#### 名詞定義
- Subject
- Subject 角色的抽象, 可能是 interface 或 abstract class
- 若為 interface, 則 IObserver 的集合則會在 ConcreteSubject 實作.
- 若為 abstract class 其可能擁有一個 IObserver 的集合. (通常為 private field)
```C#
// 紀錄 Subject 被哪些 IObserver 關注
private readonly List<IObserver> _observers = new List<IObserver>();
```
- 提供 **新增 IObserver 的公開方法** (不一定要實作)
- 如上圖中的 addObserver()
- 提供 **刪除 IObserver 的公開方法** (不一定要實作)
- 如上圖中的 removeObserver()
- 提供 **通知集合內所有 IObserver 狀態改變的公開方法** (不一定要實作)
- 如上圖中的 notifyObservers() ---> 此方法會呼叫 IObserver.notify() 使 IObserver 可以知道 Subject 的狀態改變了
- ConcreteSubject
- Subject 的具體實作, 通常會儲存 Observer 會感興趣的資料狀態
- **當達成某種條件時(e.g. 資料狀態改變時), 通知所有 Observer**
- IObserver
- Observer 角色的抽象, 可能是 interface 或 abstract class
- 提供公開方法負責 當 Subject 狀態改變時, **IObserver 所需要採取的動作** (不一定要實作)
- 如上圖中的 notify() or update()
- ConcreteObserver
- **實作 IObserver 抽象的 notify()**
- 依據 ConcreteObserver 自身是否需要取得或改變 Subject 的狀態
- 若需要 , 則其可能擁有一個 ConcreteSubject or Subject 型態的 field
```C#
public class ConcreteObserver : IObserver
{
// 之後若 ConcreteObserver 本身遇到某事情,
// 不需要等 _subject 發訊號, 即可取得 _subject 的資訊
public ConcreteObserver(ConcreteSubject subject) => _subject = subject;
}
```
- 若不需要 , 則可以透過 Notify() 傳遞 Observer 運作所需的資訊.
```C#
// Example
public class ConcreteObserver : IObserver
{
// ConcreteSubject 達成某個條件時,
// 呼叫 ConcreteObserver.Notify() 把 info 傳給 ConcreteObserver
// 變數型態是 Subject, ConcreteSubject or 簡單變數型態 or 任意 class type..
public void Notify(變數型態 info){
// do some thing by info
}
}
```
### Pseudo Code
```C#
public interface ISubject
{
void AddObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObserver();
}
public class ConcreteSubject : ISubject
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void NotifyObserver() => _observers.ForEach(observer => observer.Update());
}
public interface IObserver
{
void Update();
}
public class ConcreteObserverA : IObserver
{
public void Update()
=> Console.WriteLine($"{nameof(ConcreteObserverA)} Do Some Thing");
}
public class ConcreteObserverB : IObserver
{
public void Update()
=> Console.WriteLine($"{nameof(ConcreteObserverB)} Do Some Thing");
}
```
##### 測試程式碼
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
var concreteObserverA = new ConcreteObserverA();
var concreteObserverA1 = new ConcreteObserverA();
var concreteObserverB = new ConcreteObserverB();
subject.AddObserver(concreteObserverA);
subject.AddObserver(concreteObserverA1);//目前 2 個 ConcreteObserverA 關注 Subject
subject.AddObserver(concreteObserverB);
// Client 驅使 Subject 通知 Observers (實務上, 會需要達成某個條件, 才會呼叫此行)
subject.NotifyObserver();
Console.WriteLine($"remove {nameof(concreteObserverB)}");
subject.RemoveObserver(concreteObserverB);
subject.NotifyObserver();
Console.ReadKey();
}
}
```
##### 輸出結果
```
ConcreteObserverA Do Some Thing
ConcreteObserverA Do Some Thing
ConcreteObserverB Do Some Thing
remove concreteObserverB
ConcreteObserverA Do Some Thing
ConcreteObserverA Do Some Thing
```
## 實作時的考量點
Observer Pattern 在實作上有許多我們需要考慮的觀點, 這些觀點會影響我們實作 Observer Pattern. 以下列出一些我認為在實作 Observer Pattern 時, 需要考量的地方
### 誰負責決定通知 !?
- 由 Client 在滿足某項條件時驅使 Subject 進行通知 <-- Pseudo Code 的實作
- 由 Subject 在滿足某項條件時進行通知 e.g. 自身資料改變
- Client 在操作 Subject 時 , 不需要**負責**處理 Notify observers 的時機
#### Sample code
```C#
public abstract class Subject
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
protected void NotifyObserver()
=> _observers.ForEach(observer => observer.Update());
}
public class ConcreteSubject : Subject
{
private string _importantData = "The Data is very Cool";
public string ImportantData
{
get => _importantData;
set
{
// 當 ImportantData 被設值時, Notify All Observers
_importantData = value;
NotifyObserver();
}
}
}
public interface IObserver
{
void Update();
}
public class ConcreteObserverA : IObserver
{
public void Update()
=> Console.WriteLine($"{nameof(ConcreteObserverA)} Do Some Thing");
}
public class ConcreteObserverB : IObserver
{
public void Update()
=> Console.WriteLine($"{nameof(ConcreteObserverB)} Do Some Thing");
}
```
##### 測試程式碼
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
var concreteObserverA = new ConcreteObserverA();
var concreteObserverA1 = new ConcreteObserverA();
var concreteObserverB = new ConcreteObserverB();
subject.AddObserver(concreteObserverA);
subject.AddObserver(concreteObserverA1);
subject.AddObserver(concreteObserverB);
// ConcreteSubject 的資料改變會觸發 ConcreteSubject
// 通知所有 Observer 需要執行某項動作.
// ConcreteSubject 可在任何地方被操作
// e.g. DI 註冊成 Singleton , 再注入到複數個 class 內操作,
// 該 class 不用意識到其需要負責驅使 ConcreteSubject 通知 Observer
subject.ImportantData = "我的資料改變了 QAQ";
Console.WriteLine($"remove {nameof(concreteObserverB)}");
subject.RemoveObserver(concreteObserverB);
subject.ImportantData = "我的資料又改變了 ~~~ QAQ";
Console.ReadKey();
}
}
```
##### 輸出結果
```
ConcreteObserverA Do Some Thing
ConcreteObserverA Do Some Thing
ConcreteObserverB Do Some Thing
remove concreteObserverB
ConcreteObserverA Do Some Thing
ConcreteObserverA Do Some Thing
```
### 通知的情境為何 ?!
- 任何錯誤發生
- 資料狀態改變
- 整個流程已經完成
- ...
#### Sample Code
- 當 ConcreteSubject 遇到某個錯誤時 , 通知所有的 Observers <-- OnError
- 當 ConcreteSubject 完成運作時 , 通知所有的 Observers <-- OnCompleted
- 當 ConcreteSubject 需要通知 , 所有的 Observers 做某件事的時候 <-- OnUpdate
```C#
public interface ISubject
{
void AddObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObserver(); // you should define your method according your requirement
void Finish();
}
public class ConcreteSubject : ISubject
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void NotifyObserver() => _observers.ForEach(observer =>
{
// 使用隨機數字模擬錯誤是否發生
var isError = new Random(Guid.NewGuid().GetHashCode()).Next(2) == 0;
if (isError)
{
observer.OnError();
}
else
{
observer.OnUpdate();
}
});
public void Finish() => _observers.ForEach(observer => observer.OnCompleted());
}
public interface IObserver
{
// observer 若需要一些資料才可運作, 可透過方法參數傳入. 此處為範例. 無傳入任何資料
void OnUpdate();
void OnError();
void OnCompleted();
}
public class ConcreteObserverA : IObserver
{
public void OnUpdate()
=> Console.WriteLine($"{nameof(ConcreteObserverA)} Do Some Thing");
public void OnError()
=> Console.WriteLine($"{nameof(ConcreteObserverA)} need to handle error");
public void OnCompleted() => Console.WriteLine($"ISubject had stopped, " +
$"So {nameof(ConcreteObserverA)} should Unsubscribe");
}
public class ConcreteObserverB : IObserver
{
public void OnUpdate()
=> Console.WriteLine($"{nameof(ConcreteObserverB)} Do Some Thing");
public void OnError()
=> Console.WriteLine($"{nameof(ConcreteObserverB)} need to handle error");
public void OnCompleted() => Console.WriteLine($"ISubject had stopped, " +
$"So {nameof(ConcreteObserverB)} should Unsubscribe");
}
```
##### 測試程式碼
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
var concreteObserverA = new ConcreteObserverA();
var concreteObserverA1 = new ConcreteObserverA();
var concreteObserverB = new ConcreteObserverB();
var concreteObserverB2 = new ConcreteObserverB();
subject.AddObserver(concreteObserverA);
subject.AddObserver(concreteObserverA1);
subject.AddObserver(concreteObserverB);
subject.AddObserver(concreteObserverB2);
subject.NotifyObserver(); // notify all observers should do something
subject.Finish(); // notify all observers subject has stopped to operation
Console.ReadKey();
}
}
```
##### 輸出結果
```
ConcreteObserverA Do Some Thing
ConcreteObserverA need to handle error
ConcreteObserverB Do Some Thing
ConcreteObserverB Do Some Thing
ISubject had stopped, So ConcreteObserverA should Unsubscribe
ISubject had stopped, So ConcreteObserverA should Unsubscribe
ISubject had stopped, So ConcreteObserverB should Unsubscribe
ISubject had stopped, So ConcreteObserverB should Unsubscribe
```
### 通知的對象 !?
- Subject 通知所有的 Observer
- 可考慮讓 Observer 收到通知後, 自行決定是否應該執行動作
- **Observer 有收到通知, 但可能不做事** <-- 若通知成本昂貴, 則不適合使用此方式
```C#
public class ConcreteObserverA : IObserver
{
public void Update() {
If(Condition) {
Console.WriteLine("Do Some Thing");
}
}
}
```
- Subject 僅通知滿足條件的 Observers
- Subject 自己知道該通知哪一個 Observer
#### Sample Code
- 透過某個機制讓 Subject 知道應該通知哪些 observer.
```C#
public interface ISubject
{
// Subject 透過 filter 知道應該通知哪個 observer
void AddObserver(IObserver observer, Func<bool> filter);
void RemoveObserver(IObserver observer);
void NotifyObserver();
int GetObserverCount(); // for demo
}
public class ConcreteSubject : ISubject
{
private readonly Dictionary<IObserver, Func<bool>> _observers
= new Dictionary<IObserver, Func<bool>>();
public void AddObserver(IObserver observer, Func<bool> filter)
=> _observers.Add(observer, filter);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void NotifyObserver()
=> _observers.Where(keyValue
=> keyValue.Value.Invoke()).ToList().ForEach(keyValue
=> keyValue.Key.OnUpdate());
public int GetObserverCount() => _observers.Count;
}
public interface IObserver
{
void OnUpdate();
}
public class ConcreteObserverA : IObserver
{
public void OnUpdate()
=> Console.WriteLine($"{nameof(ConcreteObserverA)} Do Some Thing");
}
```
##### 測試程式碼
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
// the three observers will be filtered
subject.AddObserver(new ConcreteObserverA(), () => false);
subject.AddObserver(new ConcreteObserverA(), () => false);
subject.AddObserver(new ConcreteObserverA(), () => false);
Console.WriteLine($"current observer count is {subject.GetObserverCount()}");
Console.WriteLine("Call Notify Observer");
subject.NotifyObserver(); // no observer will be notified
// the observer will not be filtered
subject.AddObserver(new ConcreteObserverA(), () => true);
Console.WriteLine($"current observer count is {subject.GetObserverCount()}");
Console.WriteLine("Call Notify Observer");
subject.NotifyObserver(); // subject norify the last observer
Console.ReadKey();
}
}
```
##### 輸出結果
```
current observer count is 3
Call Notify Observer
current observer count is 4
Call Notify Observer
ConcreteObserverA Do Some Thing
```
##### 參考資料 [Java: Whats the best way to implement an observer pattern which redirects different instances to different subscriber?](https://stackoverflow.com/a/68359120/11501584)
### Observer 運作所需要的資訊應該如何取得
- 讓 Observer 取得 Subject 物件
- 讓 Observer 取得 Subject 物件時, 需要小心 Observer 是否可能誤操作 Subject 物件
```C#
public abstract class Subject
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer)
=> _observers.Add(observer);
public void RemoveObserver(IObserver observer)
=> _observers.Remove(observer);
public void NotifyAllObserver()
=> _observers.ForEach(observer => observer.Update(this));
//雖然這樣寫可以避開 StackOverFlowExecption , 但很奇怪.
//public void NotifyAllObserver(IObserver exclude = null)
// => _observers.ForEach(observer =>
// {
// if (exclude == observer)
// {
// return;
// }
// observer.Update();
// });
}
public interface IObserver
{
void Update(Subject subject);
}
public class ConcerateObserver : IObserver
{
public void Update(Subject subject)
{
// 需要擔心自己人寫錯 , 只要寫 subject.NotifyAllObserver();
// 會導致 StackOverFlowExecption
subject.NotifyAllObserver();
//雖然這樣寫可以避開 StackOverFlowExecption , 但很奇怪.
//subject.NotifyAllObserver(this);
}
}
```
- 傳遞規範的資料給 Observer E.g. Data Model
```C#
public interface IObserver
{
void Update(DataModel dto, int value, ...);
}
```
### 更新時機 !? - Push Model vs Pull Model
- 事實上 Observer Pattern 的 Push & Pull Model 以事件模型的定義嚴格地去審視, 都是事件模型的 Push Model. 但若是在 Subject Notify Observer(s) 之後, 才使用定義去判斷呢 !?
- 相同點 : Subject 需要負責在變化發生時通知 Observer
- 相異點 : Observer 在收到通知後, 是否需要自己去跟 Subject 請求自己所需的資料.
- Subject 在通知時 , 會一併給 (Push)
- Observer 需要執行某個動作之前, 才去請求 (Pull)
- Push Model
##### 事件模型的定義
> Suppliers generate events and actively pass them to an event channel. In this model, consumers wait for events to arrive from the channel.
##### Push Model 在 Observer 應用的定義
> The observer is notified that a change has occurred and must find out itself what changes have occurred. In this model, **the Subject pushes the state changes to the Observers. Push model can be used when all the Observers are interested in common state changes. Observers have no choice other than receiving the pushed data.** Push model cannot be used with large amount of data, as an Observer may not be interested in all the data that is being pushed. Also, **the Subject must know something about the Observer to which it is pushing the data. Therefore, the Observers have to comply with a standard interface required by the Subject and the Subject’s re-usability is limited to these Observers.**
- 為了方便傳送資料給 Obsevers , 通常會需要定義 Data Model
- Subject 必須知道 Observers 運作所需要的資料, 並在通知時, 一併傳送給 Observers.
```sequence
Title : Push Model
Note over Subject_Or_Client, Observer : 在通知時 , 一併傳送 Observer 所需要的資料
Subject_Or_Client ->Subject_Or_Client : call NotifyObservers(資料)
Subject_Or_Client ->Observer : 給你錢, 快做事
Observer -> Observer : 收到錢(資料)後 , 看錢決定做多少事
- Subject 傳送的資訊, Observer 不一定全部都使用到 ( Subject 會盡可能送出最詳盡的資訊 )
```C#
public class SujbetData{
public int Id {get;set;} // 有時候 Observer 可能僅需要這個屬性
public string description {get;set;} // 有時候 Observer 可能僅需要這個屬性
}
```
#### Sample Code
```C#
public class SubjectData
{
public SubjectData(int dataAccordingSubject)
=> DataAccordingSubject = dataAccordingSubject;
public int DataAccordingSubject { get; }
}
public abstract class Subject
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
// 傳送指定的資料給 observer , 而非傳入整個 Subject 的物件
protected void NotifyObserver(SubjectData data)
=> _observers.ForEach(observer => observer.Update(data));
}
public class ConcreteSubject : Subject
{
private int _importantData;
public int ImportantData
{
get => _importantData;
set
{
_importantData = value;
// 將資料轉成 Observer 接受的格式, 再傳送.
NotifyObserver(new SubjectData(ImportantData * 666));
}
}
}
public interface IObserver
{
void Update(SubjectData data);
}
public class ConcreteObserverA : IObserver
{
// 自動把自己註冊給 Subject
public ConcreteObserverA(Subject subject) => subject.AddObserver(this);
public void Update(SubjectData data)
=> Console.WriteLine($"Receive SubjectData {data.DataAccordingSubject}" +
$" , {nameof(ConcreteObserverA)} Do Some Thing");
}
```
##### 測試程式碼
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
var concreteObserverA = new ConcreteObserverA(subject);
var concreteObserverA1 = new ConcreteObserverA(subject);
subject.ImportantData = 10;
Console.WriteLine("======remove one observer==========");
subject.RemoveObserver(concreteObserverA);
subject.ImportantData = 1;
Console.WriteLine("======remove one observer==========");
subject.RemoveObserver(concreteObserverA1);
subject.ImportantData = 1;
Console.WriteLine("End");
Console.ReadKey();
}
}
```
##### 輸出結果
```
Receive SubjectData 6660 , ConcreteObserverA Do Some Thing
Receive SubjectData 6660 , ConcreteObserverA Do Some Thing
======remove one observer==========
Receive SubjectData 666 , ConcreteObserverA Do Some Thing
======remove one observer==========
End
```
- Pull Model
##### 事件模型的定義
> A consumer actively requests events from the channel. The supplier waits for a pull request to arrive from the channel. When a pull request arrives, event data is generated and returned to the channel.
##### Pull Model 在 Observer 應用的定義
> **The subject sends observers detailed information about the change that has occurred (in the simplest case, the entire new state itself).** Pull model is simple, but leads to further requests from the observer to the subject. **In this model, the Subject notifies the state change to the Observers and the Observers pull only the required data from the Subject.** Pull model is more flexible and the Observers can pull only the required data. But, more than one method call is required to implement the pull model. The first method call is for change notification from the Subject to all its Observers and an interested Observer must call at least one more method to pull the data. In a very dynamic environment, the state of the Subject can change between these two calls, that is before the Observer pulls the data. Therefore, the Subject and the Observers may not be in sync. Above all, **the Observers call specific methods to pull the required data and it is up to them to figure out what is changed without getting much help from the Subject.**
- Subject 僅需要負責通知 observer 狀態的變化, 之後由 Observer 依據自身條件決定是否向 Subject 或其他類別詢問其他更詳細的資料.
- Observer 不一定需要馬上運作. e.g. 被 Suject 通知後, Observer 新增一個 Task 到 Job Queue, 之後有空再來處理.
```sequence
Title : Pull Model
Subject_Or_Client ->Subject_Or_Client : call NotifyObservers()
Subject_Or_Client ->Observer : 通知 observers 該做事囉 ~
Observer -> Observer : 收到通知後, 依據自身情況決定什麼時候做事\n以及依據自身情況決定要取得什麼樣的資料
Observer -> Subject_Or_Client : 給我食物
Observer -> Others(不一定存在) : 我要黃金
Subject_Or_Client -> Observer: 給你
Others(不一定存在) -> Observer: 給你
Observer -> Observer : 做事
```
- Subject 通知 Observer 後, Observer 不一定會馬上執行動作, 而是自行判斷執行時機.
當其需要執行時, 才會從 Subject 取得自己所需要的資料, 再開始執行動作.
#### Sample Code
##### Subject
```C#
public abstract class SoftwareCenter
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void NotifyAllObserver()
=> _observers.ForEach(observer => observer.NotifyUpdate());
}
public interface IGameSoftware
{
public string GameDownLoadPath { get; set; }
}
public interface IAntivirusSoftware
{
public string AntivirusDownLoadPath { get; set; }
}
// 若有使用 IOC 可考慮註冊此實體為 Singleton
// 以及替 ConcreteSoftwareCenter 註冊 IGameSoftware & IAntivirusSoftware 這兩個 Key
public class ConcreteSoftwareCenter : SoftwareCenter,
IGameSoftware, IAntivirusSoftware
{
public string AntivirusDownLoadPath { get; set; } // 可能變化 !!!
= @"http://Antivirus.DownLoadPath";
public string GameDownLoadPath { get; set; } // 可能變化 !!!
= @"http://Game.DpwnLoadPath";
// 還有很多資料可以拿... 略...
}
```
##### Observer
```C#
public interface IObserver
{
void NotifyUpdate();
}
public class GameObserver : IObserver
{
private readonly IGameSoftware _gameSoftware;
private async void SimulateWaitUntilUserDownLoadGame()
{
// 執行下載前 , 需要取得遊戲的最新下載路徑.
// 假設收到通知很久後 , 使用者突然想玩遊戲 , 所以下載遊戲來玩
await Task.Delay(3000);
DownLoad();
}
public GameObserver(IGameSoftware gameSoftware) => _gameSoftware = gameSoftware;
public void DownLoad() // 取得最新下載路徑, 並下載
=> Console.WriteLine($"User Use The Path - {_gameSoftware.GameDownLoadPath} To Download Game");
public void NotifyUpdate() =>
// 收到軟體更新通知.
// 判斷是否需要下載 Game , 或啟動一個下載的排程, 若不需要則不做事.
SimulateWaitUntilUserDownLoadGame();
}
public class AntivirusObserver : IObserver
{
private readonly IAntivirusSoftware _antivirusSoftware;
private async void SimulateWaitUntilUserDownLoadAntivirus()
{
// 執行下載前 , 需要取得防毒軟體的最新下載路徑.
// 假設收到通知後, 使用者很快就下載了防毒軟體
await Task.Delay(500);
DownLoad();
}
public AntivirusObserver(IAntivirusSoftware antivirusSoftware)
=> _antivirusSoftware = antivirusSoftware;
public void DownLoad() // 取得最新下載路徑, 並下載
=> Console.WriteLine($"User Use The Path - {_antivirusSoftware.AntivirusDownLoadPath} To Get Antivirus");
public void NotifyUpdate() =>
// 收到軟體更新通知.
// 判斷是否需要下載防毒軟體 , 或啟動一個下載的排程, 若不需要則不做事.
SimulateWaitUntilUserDownLoadAntivirus();
}
```
##### 測試程式
```C#
internal static class Program
{
private static void Main(string[] args)
{
var softwareCenter = new ConcreteSoftwareCenter();
var gameObserver = new GameObserver(softwareCenter);
var antivirusObserver = new AntivirusObserver(softwareCenter);
softwareCenter.AddObserver(gameObserver);
softwareCenter.AddObserver(antivirusObserver);
softwareCenter.NotifyAllObserver();
Console.ReadKey();
}
}
```
##### 輸出結果
```
User Use The Path - http://Antivirus.DownLoadPath To Get Antivirus
User Use The Path - http://Game.DpwnLoadPath To Download Game
```
### 是否平行通知(同異步) !?
- NotifyObserver() 的實作方式通常是走訪 _observers 成員, 並且呼叫他們的 Update(), 以通知他們做事. 但若 Update() 會執行很久, 則會導致整個通知過程會很慢. 因為 Subject 必須等 Observer 做事完後, 才會繼續通知下一個 Observer.
```sequence
Title : 同步通知
Subject_Or_Client ->Observer : 快做事
Observer -> Observer : 做事
Subject_Or_Client ->Observer2 : 快做事
Observer2 -> Observer2 : 做事
Subject_Or_Client ->Observer3 : 快做事
Observer3 -> Observer3 : 做事
```
```sequence
Title : 非同步通知
Subject_Or_Client ->Observer : 快做事
Subject_Or_Client ->Observer2 : 快做事
Subject_Or_Client ->Observer3 : 快做事
Observer3 -> Observer3 : 做事
Observer2 -> Observer2 : 做事
Observer -> Observer : 做事
```
## IObservable & IObserver
C# 提供兩個介面幫助我們實作觀察者模式.
- IObservable<out T>
- IDisposable Subscribe(IObserver<T>)
- Notifies the provider that an observer is to receive notifications.
- IObserver<in T>
- void OnCompleted()
- Notifies the observer that the provider has finished sending push-based notifications.
- void OnError(Exception)
- Notifies the observer that the provider has experienced an error condition.
- void OnNext(T)
- Provides the observer with new data.
就我自己在實作觀察者模式的習慣上, 我會使用 IObserver<T> , 但卻不一定會使用 IObservable<T>.
理由是實作 IObservable 時, 通常還需要一個實作 IDisposable 類別. 此類別的 Dispose() 被呼叫時, 會將 Observable 內的 Observer 移除. 我覺得這對於後續實作者的要求太高了. 至少我是沒辦法從方法名稱看出來要實作這些...
### 微軟 Sample Code
#### Data Model
```
public class BaggageInfo
{
private int flightNo;
private string origin;
private int location;
internal BaggageInfo(int flight, string from, int carousel)
{
this.flightNo = flight;
this.origin = from;
this.location = carousel;
}
public int FlightNumber {
get { return this.flightNo; }
}
public string From {
get { return this.origin; }
}
public int Carousel {
get { return this.location; }
}
}
```
#### Unsubscriber
```C#
internal class Unsubscriber<BaggageInfo> : IDisposable
{
private List<IObserver<BaggageInfo>> _observers;
private IObserver<BaggageInfo> _observer;
internal Unsubscriber(List<IObserver<BaggageInfo>> observers,
IObserver<BaggageInfo> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observers.Contains(_observer))
_observers.Remove(_observer);
}
}
```
#### IObservable<T>
```C#
public class BaggageHandler : IObservable<BaggageInfo>
{
private List<IObserver<BaggageInfo>> observers;
private List<BaggageInfo> flights;
public BaggageHandler()
{
observers = new List<IObserver<BaggageInfo>>();
flights = new List<BaggageInfo>();
}
public IDisposable Subscribe(IObserver<BaggageInfo> observer)
{
// Check whether observer is already registered. If not, add it
if (! observers.Contains(observer)) {
observers.Add(observer);
// Provide observer with existing data.
foreach (var item in flights)
observer.OnNext(item);
}
return new Unsubscriber<BaggageInfo>(observers, observer);
}
// Called to indicate all baggage is now unloaded.
public void BaggageStatus(int flightNo)
{
BaggageStatus(flightNo, String.Empty, 0);
}
public void BaggageStatus(int flightNo, string from, int carousel)
{
var info = new BaggageInfo(flightNo, from, carousel);
// Carousel is assigned, so add new info object to list.
if (carousel > 0 && ! flights.Contains(info)) {
flights.Add(info);
foreach (var observer in observers)
observer.OnNext(info);
}
else if (carousel == 0) {
// Baggage claim for flight is done
var flightsToRemove = new List<BaggageInfo>();
foreach (var flight in flights) {
if (info.FlightNumber == flight.FlightNumber) {
flightsToRemove.Add(flight);
foreach (var observer in observers)
observer.OnNext(info);
}
}
foreach (var flightToRemove in flightsToRemove)
flights.Remove(flightToRemove);
flightsToRemove.Clear();
}
}
public void LastBaggageClaimed()
{
foreach (var observer in observers)
observer.OnCompleted();
observers.Clear();
}
}
```
#### IObserver
```C#
using System;
using System.Collections.Generic;
public class ArrivalsMonitor : IObserver<BaggageInfo>
{
private string name;
private List<string> flightInfos = new List<string>();
private IDisposable cancellation;
private string fmt = "{0,-20} {1,5} {2, 3}";
public ArrivalsMonitor(string name)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("The observer must be assigned a name.");
this.name = name;
}
public virtual void Subscribe(BaggageHandler provider)
{
cancellation = provider.Subscribe(this);
}
public virtual void Unsubscribe()
{
cancellation.Dispose();
flightInfos.Clear();
}
public virtual void OnCompleted()
{
flightInfos.Clear();
}
// No implementation needed: Method is not called by the BaggageHandler class.
public virtual void OnError(Exception e)
{
// No implementation.
}
// Update information.
public virtual void OnNext(BaggageInfo info)
{
bool updated = false;
// Flight has unloaded its baggage; remove from the monitor.
if (info.Carousel == 0) {
var flightsToRemove = new List<string>();
string flightNo = String.Format("{0,5}", info.FlightNumber);
foreach (var flightInfo in flightInfos) {
if (flightInfo.Substring(21, 5).Equals(flightNo)) {
flightsToRemove.Add(flightInfo);
updated = true;
}
}
foreach (var flightToRemove in flightsToRemove)
flightInfos.Remove(flightToRemove);
flightsToRemove.Clear();
}
else {
// Add flight if it does not exist in the collection.
string flightInfo = String.Format(fmt,
info.From, info.FlightNumber, info.Carousel);
if (! flightInfos.Contains(flightInfo)) {
flightInfos.Add(flightInfo);
updated = true;
}
}
if (updated) {
flightInfos.Sort();
Console.WriteLine("Arrivals information from {0}", this.name);
foreach (var flightInfo in flightInfos)
Console.WriteLine(flightInfo);
Console.WriteLine();
}
}
}
```
#### Test Code
```C#
using System;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
BaggageHandler provider = new BaggageHandler();
ArrivalsMonitor observer1 = new ArrivalsMonitor("BaggageClaimMonitor1");
ArrivalsMonitor observer2 = new ArrivalsMonitor("SecurityExit");
provider.BaggageStatus(712, "Detroit", 3);
observer1.Subscribe(provider);
provider.BaggageStatus(712, "Kalamazoo", 3);
provider.BaggageStatus(400, "New York-Kennedy", 1);
provider.BaggageStatus(712, "Detroit", 3);
observer2.Subscribe(provider);
provider.BaggageStatus(511, "San Francisco", 2);
provider.BaggageStatus(712);
observer2.Unsubscribe();
provider.BaggageStatus(400);
provider.LastBaggageClaimed();
}
}
// The example displays the following output:
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from SecurityExit
// Detroit 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// San Francisco 511 2
```
##### 參考資源
[Observer Design Pattern](https://docs.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern?redirectedfrom=MSDN)
[Observer Design Pattern Best Practices](https://docs.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern-best-practices)
[IObservable<T> Interface](https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=net-6.0)
[IObservable<T>.Subscribe(IObserver<T>) Method](https://docs.microsoft.com/en-us/dotnet/api/system.iobservable-1.subscribe?view=net-6.0)
[IObserver<T> Interface](https://docs.microsoft.com/en-us/dotnet/api/system.iobserver-1?view=net-6.0)
## 使用 Event / 委派 / EventHandle<T> 實作觀察者模式
- Event + 委派 , 本身就是觀察者模式的實作. 只是內化在程式碼內
```C#
private event EventHandler _myEvent;
public event EventHandler MyEvent
{
add => _myEvent += value;
remove => _myEvent -= value;
}
public void OnMyEvent() => _myEvent?.Invoke(this, EventArgs.Empty);
```
- 這個 Sample Code 只是想表示 C# 語言允許我們這麼用. 但實際情況不建議這麼使用.
```C#
public interface IObserver
{
public void Notify(ConcreteSubject subject);
}
public class ConcreteObserver1 : IObserver
{
public void Notify(ConcreteSubject subject)
=> Console.WriteLine($"{Name} IObserver Subject's data : {subject.ImportantData}");
public string Name { get; set; }
}
public class ConcreteObserver2 : IObserver
{
public void Notify(ConcreteSubject subject)
=> Console.WriteLine($"{Title} vs {subject.ImportantData}");
public string Title { get; set; }
}
public class ConcreteSubject
{
private string _importantData;
public string ImportantData
{
get => _importantData;
set
{
_importantData = value;
Notify?.Invoke(this);
}
}
// 可使用 Delegate(Func or Action 等) or EventHandler(也是一種 Delegate) 任一者替換
// event 可限制 client 端只能使用 += or -= , 避免 Client 使用 Notify = null (防呆)
public event Action<ConcreteSubject> Notify;
}
```
##### Client
```C#
internal static class Program
{
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
var concreteObservers =
new IObserver[] { new ConcreteObserver1 { Name = "Observer1", },
new ConcreteObserver2 { Title = "Observer2", }, };
foreach (var observer in concreteObservers) // make Observers Waching subject
{
subject.Notify += observer.Notify;
}
subject.ImportantData = "Important Data Changed";
subject.Notify -= concreteObservers[0].Notify; // Observer1 don't watch subject
subject.ImportantData = "Important Data Changed 2";
Console.ReadLine();
}
}
```
##### 輸出結果
```
Observer1 IObserver Subject's data : Important Data Changed
Observer2 vs Important Data Changed
Observer2 vs Important Data Changed 2
```
- 由 Client 端自己控制 誰會觀察 ConcreteSubject
- 也可以讓 Client 自己觀察就好
```C#
subject.Notify += MethodAtClient
```
- 需要考慮給 Client 端這麼大的自由是否合適 !!!
- Client 必須自己負責移除觀察者 , 否則可能會有 Memory Leak 問題.
```C#
public class ConcreteObserver
{
private readonly byte[] _buffBytes = new byte[5 * 1024 * 1024];
public void Notify() =>
Console.WriteLine($"{GC.GetTotalMemory(false)/(1024 * 1024)}MB vs {DateTime.Now}");
}
public class ConcreteSubject
{
private string _importantData;
public string ImportantData
{
get => _importantData;
set
{
_importantData = value;
Notify?.Invoke();
}
}
public event Action Notify;
}
```
##### Client
```C#
private static void Main(string[] args)
{
var subject = new ConcreteSubject();
// 建立物件前記憶體使用量(MB為單位)
Console.WriteLine($"建立物件前 : {GC.GetTotalMemory(false) / (1024 * 1024)}MB");
var concreteObserver1 = new ConcreteObserver();
var concreteObserver2 = new ConcreteObserver();
// 建立物件後記憶體使用量(MB為單位)
Console.WriteLine($"建立物件後 : {GC.GetTotalMemory(false) / (1024 * 1024)}MB");
// 註冊 event & 確保 event raise 時 , ConcreteObserver 會做事
subject.Notify += concreteObserver1.Notify;
subject.Notify += concreteObserver2.Notify;
subject.ImportantData = "Important Data Changed";
//subject.Notify -= concreteObserver1.Notify;
//subject.Notify -= concreteObserver2.Notify;
// 釋放物件 concreteObserver1 & concreteObserver2 並且強制
// GC 檢查無使用物件, 並回收他們的記憶體
concreteObserver1 = null;
concreteObserver2 = null;
GC.Collect();
// GC 檢查後 , 記憶體使用量(MB為單位)
Console.WriteLine($"GC 檢查 : {GC.GetTotalMemory(false) / (1024 * 1024)}MB");
Console.ReadLine();
}
```
##### 輸出結果
```
建立物件前 : 0MB
建立物件後 : 10MB
10MB vs 2022/3/18 上午 12:28:37
10MB vs 2022/3/18 上午 12:28:37
GC 檢查 : 10MB
```
- 此核心問題點是訂閱者的生命週期比事件提供者還要短
- 即使 Client 不使用 concreteObserver1 & concreteObserver2 但 subject 仍然紀錄 concreteObserver1 & concreteObserver2 的物件位址 , 導致 GC 認為這兩個物件有被使用, 所以不回收.
- 為了解決此問題 , 必須解除 concreteObserver1 & concreteObserver2 對 subject 的訂閱.
```C#
subject.Notify -= concreteObserver1.Notify;
subject.Notify -= concreteObserver2.Notify;
```
## 用 Observer Pattern 模擬 WPF 的 Binding 機制
### DataWithPropertyChanged.cs
```C#
public class DataWithPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _prop;
private string _prop2;
public string Prop
{
get => _prop;
set
{
_prop = value;
OnPropertyChanged(nameof(Prop));
}
}
public string Prop2
{
get => _prop2;
set
{
_prop2 = value;
OnPropertyChanged(nameof(Prop2));
}
}
public void OnPropertyChanged(string name)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
```
### BindingSimulation.cs
```C#
public class BindingSimulation
{
public string SourcePropertyName { get; set; }
public string TargetObjectName { get; set; }
public string TargetPropertyName { get; set; }
}
```
### MainWindow.cs
```C#
public partial class MainWindow : Window
{
public DataWithPropertyChanged Source { get; }
public List<BindingSimulation> Bindings { get; set; } = new List<BindingSimulation>
{
// Correct
new BindingSimulation { SourcePropertyName = "Prop",
TargetObjectName = "Output1", TargetPropertyName = "Text", },
new BindingSimulation { SourcePropertyName = "Prop",
TargetObjectName = "Output2", TargetPropertyName = "Text", },
new BindingSimulation { SourcePropertyName = "Prop",
TargetObjectName = "Output3", TargetPropertyName = "Content", },
new BindingSimulation { SourcePropertyName = "Prop2",
TargetObjectName = "Output4", TargetPropertyName = "Text", },
// Wrong ,
new BindingSimulation { SourcePropertyName = "Prop",
TargetObjectName = "Output3", TargetPropertyName = "ContentQQQ", },
new BindingSimulation { SourcePropertyName = "NameQ",
TargetObjectName = "Output1", TargetPropertyName = "Text", },
new BindingSimulation { SourcePropertyName = "Prop",
TargetObjectName = "Output9", TargetPropertyName = "Text", },
new BindingSimulation { SourcePropertyName = "NameQ",
TargetObjectName = "Output99", TargetPropertyName = "Text", },
};
public MainWindow()
{
InitializeComponent();
Source = new DataWithPropertyChanged();
if (Source is INotifyPropertyChanged propertyChanged)
{
propertyChanged.PropertyChanged += (sender, e) =>
{
var inputPropertyInfo = sender.GetType().GetProperty(e.PropertyName);
if (inputPropertyInfo == null || !inputPropertyInfo.CanRead)
{
return;
}
var inputValue = inputPropertyInfo.GetValue(sender);
Bindings.Where(bingingObj
=> bingingObj.SourcePropertyName == inputPropertyInfo.Name)
.ToList()
.ForEach(bindingObject =>
{
var viewElement = FindName(bindingObject.TargetObjectName);
if (viewElement == null)
{
return;
}
var viewTypePropertyInfo = viewElement.GetType()
.GetProperty(bindingObject.TargetPropertyName);
viewTypePropertyInfo?.SetValue(viewElement, inputValue);
});
};
}
}
private void UpdateBtn_OnClick(object sender, RoutedEventArgs e)
{
var input1Text = Input1.Text;
var input2Text = Input2.Text;
Source.Prop = input1Text;
Source.Prop2 = input2Text;
}
}
```
### MainWindow.xaml
```XAML
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBox Name="Input1"/>
<TextBox Name="Input2"/>
<Button Name="UpdateBtn" Content="Update" Click="UpdateBtn_OnClick"/>
<TextBlock Name="Output1"/>
<TextBlock Name="Output2"/>
<Button Name="Output3" Content=""/>
<TextBlock Name="Output4"/>
</StackPanel>
</Window>
```
### 輸出結果

## Rx NET
##### 前言
這東西水很深...
我研究了很久, 還是沒辦法很好地整理出一份筆記. 我就爛 (・ัω・ั)♡
### 安裝必須
```
System.Reactive.* <--- 裝這個, 都可
System.Reactive.Linq <--- 建議直接裝這個.
```
### Sample Code
```C#
public interface IAggregatorSubject : IDisposable
{
bool HasSubscriber { get; }
IDisposable Subscribe<TEvent>(Action<TEvent> action);
void Publish<TEvent>(TEvent sampleEvent);
Task PublishAsync<TEvent>(TEvent sampleEvent);
}
public class AggregatorSubject : IAggregatorSubject
{
private bool _disposed;
private readonly Subject<object> _subject = new Subject<object>();
private IObservable<TEvent> GetEvent<TEvent>()
=> _subject.OfType<TEvent>().AsObservable();
public bool HasSubscriber => _subject.HasObservers;
public IDisposable Subscribe<TEvent>(Action<TEvent> action)
=> GetEvent<TEvent>().Subscribe(action);
public void Publish<TEvent>(TEvent sampleEvent) => _subject.OnNext(sampleEvent);
public Task PublishAsync<TEvent>(TEvent sampleEvent)
=> Task.Run(() => Publish(sampleEvent));
public void Dispose() => Dispose(true);
public virtual void Dispose(bool disposing)
{
if (_disposed) { return; }
if (disposing) { _subject.Dispose(); } //Dispose managed state (managed objects)
_disposed = true;
}
}
internal static class Program
{
private static void Main(string[] args)
{
var subject = new AggregatorSubject();
// only Subscribe string type data, so you can define your customized data model
var subscriber =
subject.Subscribe<string>((d)=>Console.WriteLine($"receive data : {d}"));
Console.WriteLine("notify int data");
subject.Publish(1);
Console.WriteLine("notify string data");
subject.Publish("444999");
Console.WriteLine("unsubscribe");
subscriber.Dispose();
Console.WriteLine("notify string data");
subject.Publish("data");
Console.ReadKey();
}
}
```
##### 輸出
```
notify int data
notify string data
receive data : 444999
unsubscribe
notify string data
```
#### 參考資源
[[C#] Rx.NET 筆記 (不定時更新)](https://fullstackladder.dev/blog/2021/04/17/notes-for-rx-net/)
[Rx.NET 简介](https://cloud.tencent.com/developer/article/1099766)
[4. Rxjs 介绍及注意事项](https://cloud.tencent.com/developer/article/1378975?from=article.detail.1099766)
[响应式编程知多少 | Rx.NET 了解下](https://cloud.tencent.com/developer/article/1436791?from=article.detail.1099766)
[[C#]使用rx.net來幫助完成主動通知和訂閱的功能](https://dotblogs.com.tw/kinanson/2017/11/03/005232)
[Rx應用](https://gihomn.blogspot.com/2017/04/rxing.html#%E5%B0%8D%E4%BA%8B%E4%BB%B6%E5%8F%96%E6%A8%A3)
[ReactiveX](https://reactivex.io/intro.html)
[Observable](https://reactivex.io/documentation/observable.html)
[Reactive Extensions Observables Versus Regular .NET Events Part 1](http://mark-dot-net.blogspot.com/2014/04/reactive-extensions-observables-versus.html)
[Reactive Extensions (Rx) - Part 1 - Replacing C# Events](https://rehansaeed.com/reactive-extensions-part1-replacing-events/)
[Reactive Extensions (Rx) - Part 2 - Wrapping C# Events](https://rehansaeed.com/reactive-extensions-part2-wrapping-events/)
[Reactive Extensions (Rx) - Part 3 - Naming Conventions](https://rehansaeed.com/reactive-extensions-part3-naming-conventions/)
[Reactive Extensions (Rx) - Part 4 - Replacing Timers](https://rehansaeed.com/reactive-extensions-part4-replacing-timers/)
[Reactive Extensions (Rx) - Part 5 - Awaiting Observables](https://rehansaeed.com/reactive-extensions-part5-awaiting-observables/)
[Reactive Extensions (Rx) - Part 6 - Task ToObservable](https://rehansaeed.com/reactive-extensions-part6-task-toobservable/)
[Reactive Extensions (Rx) - Part 7 - Sample Events](https://rehansaeed.com/reactive-extensions-part7-sample-events/)
[Reactive Extensions (Rx) - Part 8 - Timeouts](https://rehansaeed.com/reactive-extensions-rx-part-8-timeouts/)
[Introduction to Rx](http://introtorx.com/Content/v1.0.10621.0/01_WhyRx.html)
[Why is it possible to await an Rx observable? [duplicate]](https://stackoverflow.com/a/27415142/11501584)
[How to make the Rx callbacks run on the ThreadPool?](https://stackoverflow.com/a/66315740/11501584)
[ReactiveUI](https://www.reactiveui.net/docs/getting-started/)
[reactive github](https://github.com/dotnet/reactive)
## 結論
- 一個 Observer 可訂閱複數個 Subject, 一個 Subject 可以讓複數個 Observer 訂閱.
- Subject 和 Observer 可被個別修改 (兩者的接觸僅有資料的傳遞)
- 加入新的 Observer 時, 不會影響到 Subject 與原有的 Observers
- Subject 和 Observer 是抽象耦合的關西
- Subject 不需要知道具體實作介面 Observer 的類別 Concrete class 是哪一個
- 支援 Brocast
- Subject 傳送訊息時, 不需要指定接收者是誰, 而是會自動 Brocast 給所有有訂閱的 Observer
- Observer Pattern 通常使用在 Subject 和 Observer 無法輕易取得對方資訊時
- 例如 : 當 Subject 和 Observer 放置在同一個類別內時, 亙本就不需要使用 XD
```mermaid
graph TB
subgraph 同一個 class 內
Subject
Observe
end
```
- 決定是否**能使用** Observer Patter 的一個因素是**你是否能夠修改 Subject 的程式碼**
- 若可以 , 則可以考慮使用
```mermaid
graph LR
Subject -- Notify --> Observe
```
- 若不行 , 則只能讓 Observe 定時去跟 Subject 請求資料
```mermaid
graph RL
Observe -- Poll Data --> Subject
```
## 參考
[Programming Patterns Overview](https://kremer.cpsc.ucalgary.ca/patterns/)
[Observer](https://refactoring.guru/design-patterns/observer)
[What would be the best way to implement change tracking on an object](https://stackoverflow.com/a/2363851/11501584)
[Observer Pattern C#](https://codewithshadman.com/observer-pattern-csharp/#pull)
[Observer Pattern - Push vs Pull Model](https://kktechkaizen.blogspot.com/2009/05/observer-pattern-push-vs-pull-model.html?m=1&fbclid=IwAR1SzDiHN_dSEQjhn6rtItL67S3KlAXwDwlvDpJBc04P2JoXKyb-94N1I4k)
[Observer Design Pattern Tutorial](https://blog.devgenius.io/observer-pattern-explanation-a-trial-of-blending-theory-with-practical-d68c8a482f38)
[The Observer Pattern](https://dzone.com/articles/observer-pattern)
[Push, pull mechanism observer pattern](https://stackoverflow.com/a/45656811/11501584)
[Event Driven Programming in C# — Observer Pattern](https://levelup.gitconnected.com/event-driven-programming-in-c-observer-pattern-7a8d790bbdb2)
[[Design Pattern] 觀察者模式 (Observer Pattern) 我也能夠辦報社](https://dotblogs.com.tw/joysdw12/2013/03/13/96531)
[觀察者模式 (Observer Pattern)](https://notfalse.net/10/observer-pattern#ConcreteObserver-ex)
[[ Day 8 ] 初探設計模式 - 觀察者模式 ( Observer Pattern )](https://ithelp.ithome.com.tw/articles/10204117)
[Design Pattern | 只要你想知道,我就告訴你 - 觀察者模式( Observer Pattern ) feat. TypeScript](https://medium.com/enjoy-life-enjoy-coding/design-pattern-%E5%8F%AA%E8%A6%81%E4%BD%A0%E6%83%B3%E7%9F%A5%E9%81%93-%E6%88%91%E5%B0%B1%E5%91%8A%E8%A8%B4%E4%BD%A0-%E8%A7%80%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F-observer-pattern-feat-typescript-8c15dcb21622)
[WPF利用VisualTreeHelper遍歷尋找物件的子級物件或者父級物件](https://www.796t.com/content/1549038455.html)
[Find all controls in WPF Window by type](https://stackoverflow.com/questions/974598/find-all-controls-in-wpf-window-by-type)
[Find Controls by Name in WPF](https://www.c-sharpcorner.com/uploadfile/mahesh/find-controls-by-name-in-wpf/)
[使用 Weak Events 來避免記憶體洩漏問題](https://www.huanlintalk.com/2012/09/use-weak-events-to-avoid-memory-leak.html)
[可能會導致.NET記憶體洩露的8種行為](https://iter01.com/505954.html)
---
###### Thank you!
You can find me on
- [GitHub](https://github.com/s0920832252)
- [Facebook](https://www.facebook.com/fourtune.chen)
若有謬誤 , 煩請告知 , 新手發帖請多包涵
# :100: :muscle: :tada: :sheep:
<iframe src="https://skilltree.my/c67b0d8a-9b69-47ce-a50d-c3fc60090493/promotion?w=250" width="250" style="border:none"></iframe>