講故事(user story) 假設我們是一個優惠券提供平臺,故事就發生在顧客在我們平臺採購完成支付成功後。 支付完成後平臺要進行的一些操作: 1. 簡訊通知客戶已經生成訂單 2. 增加顧客的積分 3. 開始按訂單需求制券 。。。(可能會有許多操作) 接下來就是將故事以代碼的形式展現出來。。。 ...
講故事(user story)
假設我們是一個優惠券提供平臺,故事就發生在顧客在我們平臺採購完成支付成功後。
支付完成後平臺要進行的一些操作:
-
簡訊通知客戶已經生成訂單
-
增加顧客的積分
-
開始按訂單需求制券
。。。(可能會有許多操作)
接下來就是將故事以代碼的形式展現出來。。。
需求分析
我們將上述故事轉化為代碼中的對象分別是: 支付成功 PaySuccessSubject、簡訊通知 MessageObserver、積分增加 BonusObserver、制券 CouponObserver。
當支付成功PaySuccessSubject後,要通知到MessageObserver,BonusObserver,CouponObserver這三個對象,為了實現上面這個需求,將採用觀察者模式(發佈-訂閱)
敲黑板.劃重點
觀察者模式又叫發佈-訂閱(Publish/Subscribe)模式,定義了一種一對多的依賴關係,讓多個觀察者對象同時 監聽某一個主題對象。當主題對象在狀態發生變化時,通知所有觀察者對象,使他們能夠自己更新自己。
Show Code
Subject類,把所有觀察者對象的引用保存在一個集合了,每個通知者都可以有任何數量的觀察者。抽象通知者提供 可以增加和刪除觀察者對象的介面。
/// <summary> /// 抽象通知者 /// </summary> public abstract class Subject { /// <summary> /// 觀察者集合 /// </summary> protected List<IObserver> observers = new List<IObserver>(); public string State { get; set; } /// <summary> /// 添加觀察者 /// </summary> /// <param name="observer">觀察者</param> public void Attach(IObserver observer) { observers.Add(observer); } /// <summary> /// 刪除觀察者 /// </summary> /// <param name="observer">觀察者</param> public void Detach(IObserver observer) { observers.Remove(observer); } /// <summary> /// 通知 /// </summary> /// <returns></returns> public void Notify() { foreach (var observer in observers) { observer.Update(); } } }
PaySuccessSubject類,具體的通知者,給所有登記過的觀察者發出通知。
/// <summary> /// 支持成功通知者 /// </summary> public class PaySuccessSubject : Subject { }
Observer類,抽象觀察者,為所有的具體的觀察者定義一個介面,一般用抽象類或介面實現。通常包含一個Update()更新方法。
/// <summary> /// 抽象觀察 /// </summary> public abstract class Observer { public abstract void Update(); }
MessageObserver、BonusObserver、CouponObserver具體的觀察者類,實現更新介面,以便本身的狀態與主題的狀態相協調。
/// <summary> /// 簡訊觀察者 /// </summary> public class MessageObserver : Observer { public Subject Subject { get; set; } public MessageObserver(Subject subject) { Subject = subject; } public override void Update() { Console.WriteLine($"{Subject.State}:簡訊通知了..."); } } /// <summary> /// 積分觀察者 /// </summary> public class BonusObserver : Observer { public Subject Subject { get; set; } public BonusObserver(Subject subject) { Subject = subject; } public override void Update() { Console.WriteLine($"{Subject.State}:積分增加了..."); } } /// <summary> /// 券觀察者 /// </summary> public class CouponObserver : Observer { public Subject Subject { get; set; } public CouponObserver(Subject subject) { Subject = subject; } public override void Update() { Console.WriteLine($"{Subject.State}:開始制券了..."); } }
客戶端代碼
private static void Main(string[] args) { var subject = new PaySuccessSubject(); var observer1 = new CouponObserver(subject); var observer2 = new MessageObserver(subject); var observer3 = new BonusObserver(subject); //添加訂閱 subject.Attach(observer1); subject.Attach(observer2); subject.Attach(observer3); //發佈通知 subject.State = "星巴克10十元券採購成功"; subject.Notify(); Console.WriteLine("\n\nHappy Ending~"); Console.ReadLine(); }
結果顯示
Code Upgrade
code review後發現,在通知給觀察者時,是順序執行,如果其中一個觀察者卡頓或者錯誤,會導致其他觀察者卡克,所以我們應該採用非同步方式。
下麵我們將模擬 制券過程耗時增加,但不影響通知其他觀察者。直接上代碼:
/// <summary>
/// 抽象觀察
/// </summary>
public abstract class Observer
{
public abstract Task UpdateAsync();
}
/// <summary>
/// 簡訊觀察者
/// </summary>
public class MessageObserver : Observer
{
public Subject Subject { get; set; }
public MessageObserver(Subject subject)
{
Subject = subject;
}
public override Task UpdateAsync()
{
Console.WriteLine($"{Subject.State}:簡訊通知了...");
return Task.CompletedTask;
}
}
/// <summary>
/// 積分觀察者
/// </summary>
public class BonusObserver : Observer
{
public Subject Subject { get; set; }
public BonusObserver(Subject subject)
{
Subject = subject;
}
public override Task UpdateAsync()
{
Console.WriteLine($"{Subject.State}:積分增加了...");
return Task.CompletedTask;
}
}
/// <summary>
/// 券觀察者
/// </summary>
public class CouponObserver : Observer
{
public Subject Subject { get; set; }
public CouponObserver(Subject subject)
{
Subject = subject;
}
public override async Task UpdateAsync()
{
Console.WriteLine($"{Subject.State}:開始制券...");
//模擬制券耗時
await Task.Delay(3000);
Console.WriteLine($"{Subject.State}:制券完成...");
}
}
/// <summary>
/// 抽象通知者
/// </summary>
public abstract class Subject
{
/// <summary>
/// 觀察者集合
/// </summary>
protected List<Observer> observers = new List<Observer>();
public string State { get; set; }
/// <summary>
/// 添加觀察者
/// </summary>
/// <param name="observer">觀察者</param>
public void Attach(Observer observer)
{
observers.Add(observer);
}
/// <summary>
/// 刪除觀察者
/// </summary>
/// <param name="observer">觀察者</param>
public void Detach(Observer observer)
{
observers.Remove(observer);
}
/// <summary>
/// 通知
/// </summary>
/// <returns></returns>
public Task Notify()
{
foreach (var observer in observers)
{
observer.UpdateAsync();
}
return Task.CompletedTask;
}
}
客戶端端代碼:
private static async Task Main(string[] args)
{
var subject = new PaySuccessSubject();
var observer1 = new CouponObserver(subject);
var observer2 = new MessageObserver(subject);
var observer3 = new BonusObserver(subject);
//添加訂閱
subject.Attach(observer1);
subject.Attach(observer2);
subject.Attach(observer3);
//發佈通知
subject.State = "星巴克10十元券採購成功";
await subject.Notify();
Console.WriteLine("\n\nHappy Ending~");
Console.ReadLine();
}
結果顯示:
委托加持觀察者模式
現實開發中,很多觀察者對象共同繼承或者實現同一個抽象觀察者,不合適;並且所有觀察者對象的操作方法統一叫一個 Update(),達不到望文生義的效果,所以我們對觀察者模式再次進行升級,使用委托來替換掉抽象觀察者,
直接上代碼:
/// <summary>
/// 簡訊觀察者
/// </summary>
public class MessageObserver
{
public ISubject Subject { get; set; }
public MessageObserver(ISubject subject)
{
Subject = subject;
}
/// <summary>
/// 發送簡訊
/// </summary>
/// <returns></returns>
public Task SendMessageAsync()
{
Console.WriteLine($"{Subject.State}:簡訊通知了...");
return Task.CompletedTask;
}
}
/// <summary>
/// 積分觀察者
/// </summary>
public class BonusObserver
{
public ISubject Subject { get; set; }
public BonusObserver(ISubject subject)
{
Subject = subject;
}
/// <summary>
/// 添加積分
/// </summary>
/// <returns></returns>
public Task AddBonusAsync()
{
Console.WriteLine($"{Subject.State}:積分增加了...");
return Task.CompletedTask;
}
}
/// <summary>
/// 券觀察者
/// </summary>
public class CouponObserver
{
public ISubject Subject { get; set; }
public CouponObserver(ISubject subject)
{
Subject = subject;
}
/// <summary>
/// 制券
/// </summary>
/// <returns></returns>
public async Task MakeCouponAsync()
{
Console.WriteLine($"{Subject.State}:開始制券...");
//模擬制券耗時
await Task.Delay(3000);
Console.WriteLine($"{Subject.State}:制券完成...");
}
}
/// <summary>
/// 抽象通知者
/// </summary>
public interface ISubject
{
/// <summary>
/// 通知
/// </summary>
/// <returns></returns>
public Task Notify();
public string State { get; set; }
}
/// <summary>
/// 支持成功通知者
/// </summary>
public class PaySuccessSubject : ISubject
{
public Func<Task> Update;
public string State { get; set; }
public Task Notify()
{
Update();
return Task.CompletedTask;
}
}
}
客戶端調用:
internal class Program
{
private static async Task Main(string[] args)
{
var subject = new ObserverDelegate.PaySuccessSubject();
var observer1 = new ObserverDelegate.CouponObserver(subject);
var observer2 = new ObserverDelegate.MessageObserver(subject);
var observer3 = new ObserverDelegate.BonusObserver(subject);
//添加訂閱
subject.Update += observer1.MakeCouponAsync;
subject.Update += observer2.SendMessageAsync;
subject.Update += observer3.AddBonusAsync;
//發佈通知
subject.State = "星巴克10十元券採購成功";
await subject.Notify();
Console.WriteLine("\n\nHappy Ending~");
Console.ReadLine();
}
}
}
展示結果和上面一樣。
源碼地址:https://gitee.com/sayook/DesignMode/tree/master/Observer