關於 Observer 本篇將討論以下幾個問題
1. 關於 Observer
2. UML
3. 將 UML 轉為程式碼
4. 情境
測試環境:
OS:Windows 10
IDE:Visual Studio 2019
1. 關於 Observer
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
by Gang of Four
- 在物件間定義一對多依賴關係,當一個物件改變狀態時,所有依賴關係都會被通知並自動更新
Observer(觀察者)屬於行為型(Behavioral Patterns),當遇到自身狀態異動就會有其他物件要跟著處理時,可藉由 Observer 來處理通知邏輯,將「狀態異動的物件」與「要跟著處理的物件」解除耦合,之後新增一個「要跟著處理的物件」時,直接訂閱通知即可。
優點:
- 符合 開閉原則(Open Closed Principle)
缺點:
- 維護上較不易,錯誤的修改可能造成訂閱者收到非預期通知卻不易察覺
2. UML
Class 間關聯:
- Subject 關聯 Observer
- ConcreteSubject 繼承 Subject
- ConcreteObserver 繼承 Observer
- ConcreteObserver 關聯 ConcreteSubject
Class:
- Subject:
- ConcreteSubject:
- Observer:
- ConcreteObserver:
3. 將 UML 轉為程式碼
Subject 的抽象類別,實作加入 & 移除觀察者
/// <summary>
/// Subject 的抽象類別,實作加入 & 移除觀察者
/// </summary>
public abstract class Subject
{
private List<IObserver> _observers = new List<IObserver>();
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
foreach (IObserver observer in _observers)
{
observer.Update();
}
}
}
Subject 實作
/// <summary>
/// Subject 實作
/// </summary>
public class ConcreteSubject : Subject
{
public string SubjectState { get; set; }
}
觀察者介面
/// <summary>
/// 觀察者介面
/// </summary>
public interface IObserver
{
void Update();
}
觀察者實作
/// <summary>
/// 觀察者實作
/// </summary>
public class ConcreteObserver : IObserver
{
public ConcreteSubject Subject { get; set; }
private string _name;
private string _observerState;
public ConcreteObserver(ConcreteSubject subject, string name)
{
Subject = subject;
_name = name;
}
public void Update()
{
_observerState = Subject.SubjectState;
Console.WriteLine($"Observer {_name}'s new state is {_observerState}");
}
}
- 建立
originator
並給定初始值 On - 建立
caretaker
並透過originator
建立Memento
- 改變
State
的值 - 透過
Memento
還原State
的值
static void Main(string[] args)
{
Default.ConcreteSubject subject = new Default.ConcreteSubject();
subject.Attach(new Default.ConcreteObserver(subject, "X"));
subject.Attach(new Default.ConcreteObserver(subject, "Y"));
subject.Attach(new Default.ConcreteObserver(subject, "Z"));
subject.SubjectState = "ABC";
subject.Notify();
Console.ReadLine();
}
執行結果
Observer X's new state is ABC
Observer Y's new state is ABC
Observer Z's new state is ABC
4. 情境
我們接到了一個缺貨商品到貨通知的需求
- 商品到貨狀態改變時通知有點選到貨通知的客戶
到貨狀態 enum
public enum StockType
{
OutOfStock,
InStock
}
商品抽象類別,實作加入 & 移除觀察者
/// <summary>
/// 商品抽象類別,實作加入 & 移除觀察者
/// </summary>
public abstract class Product
{
private List<IObserver> _observers = new List<IObserver>();
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify()
{
foreach (IObserver observer in _observers)
{
observer.Update();
}
}
}
商品介面,紀錄商品名稱 & 到貨狀態
public interface IProduct
{
string Name { get; }
StockType StockType { get; set; }
}
商品實作
/// <summary>
/// 商品實作
/// </summary>
public class AppleProduct : Product, IProduct
{
public string Name { get; }
public StockType StockType { get; set; }
public AppleProduct(string name)
{
Name = name;
}
}
觀察者介面
/// <summary>
/// 觀察者介面
/// </summary>
public interface IObserver
{
void Update();
}
觀察者實作
/// <summary>
/// 觀察者實作
/// </summary>
public class ConcreteObserver<T> : IObserver
where T : IProduct
{
private T Product { get; }
private string _customerName;
private StockType _stockType;
public ConcreteObserver(T product, string customerName)
{
Product = product;
_customerName = customerName;
}
public void Update()
{
_stockType = Product.StockType;
Console.WriteLine($"Hi {_customerName}, {Product.Name} is {_stockType}.");
}
}
- 建立商品
iPhone
&iPad
- 將商品狀態設為
StockType.OutOfStock
- 客戶點選
iPhone
&iPad
到貨通知 - 商品狀態改變,通知客戶
static void Main(string[] args)
{
Situation.AppleProduct iPhone = new Situation.AppleProduct("iPhone");
iPhone.StockType = Situation.StockType.OutOfStock;
Situation.AppleProduct iPad = new Situation.AppleProduct("iPad");
iPad.StockType = Situation.StockType.OutOfStock;
iPhone.Attach(new Situation.ConcreteObserver<Situation.AppleProduct>(iPhone, "John"));
iPhone.Attach(new Situation.ConcreteObserver<Situation.AppleProduct>(iPhone, "Wayne"));
iPad.Attach(new Situation.ConcreteObserver<Situation.AppleProduct>(iPad, "Leo"));
iPad.Attach(new Situation.ConcreteObserver<Situation.AppleProduct>(iPad, "Henry"));
iPhone.StockType = Situation.StockType.InStock;
iPad.StockType = Situation.StockType.InStock;
iPhone.Notify();
iPad.Notify();
Console.ReadLine();
}
執行結果
Hi John, iPhone is InStock.
Hi Wayne, iPhone is InStock.
Hi Leo, iPad is InStock.
Hi Henry, iPad is InStock.
完整程式碼
GitHub:Behavioral_07_Observer