系列文章的目錄:https://blog.csdn.net/hjkl950217/article/details/89490709
記住:設計模式重理解,輕照搬
業務背景
想象一下你在做一個簡單的電商系統,電商系統中核心一定會有商品、訂單這2個東西,而商品必然會有數量、品牌、商品描述信息這些方面。而商品上架之後纔會有訂單這個東西,那麼我們第一件事就是進行商品管理。一個簡單的商品創建模型如下:
隨着業務變化,你發現商品的不同品牌之間有着關聯。比如AMD(中國)和AMD(美國)可以看做一個公司,但在具體商品上又要區分開,記錄上主公司與分公司的關係。那麼我們的模型發生了一點變化:
設計
在開始之前,建議先停下來想想,如果是你,你會如何設計? 然後再向下看。
觀察上面整理出來的業務模型發現特點:一環扣一環,審批處如果審批不過會退回到起始點。
我們這裏簡單對比下傳統思路與責任鏈模式:
- 如果用傳統的業務思想去開發:可能會是一個方法中調用不同的子方法,然後判斷每個子方法的返回結果並做出不同的處理方式。隨着業務變化,這個方法會變的越來越龐大,修改一處都有可能影響整套代碼。
- 如果使用責任鏈模式:把每一環拆開進行開發,然後在使用處組裝成我們的業務處理鏈。這樣在開發時我們並不關心整體如何,而是我們這一處地方應該如何。並且業務變化時,中斷整體調用,由具體的環節給出對應的方案。
下面是實現的代碼,你也可以下載查看 1。
首先是我們的實體類:
/// <summary>
/// 商品信息
/// </summary>
public class ItemInfo
{
/// <summary>
/// 商品名
/// </summary>
public string Name { get; set; }
}
設計一個處理者接口(也可以使用父類):
/// <summary>
/// 商品創建處理者
/// </summary>
public interface IItemCreateHandler
{
/// <summary>
/// 下一個處理者
/// </summary>
IItemCreateHandler NextHandler { get; }
/// <summary>
/// 設置下一個處理者
/// </summary>
/// <param name="handler"></param>
void SetNextHandler(IItemCreateHandler handler);
/// <summary>
/// 處理商品創建
/// </summary>
/// <param name="itemInfo"></param>
bool HandleItemCreate(ItemInfo itemInfo);
}
實現的代碼就很簡單了:
- 處理錄入的信息
- 審批
- 持久化到數據庫
代碼:
/// <summary>
/// 商品信息錄入處理器
/// </summary>
public class ItemInfoEntryHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
itemInfo = itemInfo ?? this.GetItemInfoFromUI();
return this.NextHandler.HandleItemCreate(itemInfo);
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
//模擬從UI上獲取數據
private ItemInfo GetItemInfoFromUI()
{
return new ItemInfo() { };
}
}
/// <summary>
/// 商品創建審批處理者
/// </summary>
public class ItemCreateApproveHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//做一些檢查,比如名字、信息是否匹配等
//實際中,這些檢查是由管理者在UI上審覈的,這裏只需要接受審覈結果即可
if (string.IsNullOrWhiteSpace(itemInfo.Name))
{
Console.WriteLine("商品名不能爲空,請重新輸入");
return false;
}
else
{
return this.NextHandler.HandleItemCreate(itemInfo);
}
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
/// <summary>
/// 商品信息持久化處理者
/// </summary>
public class ItemInfoPersistenceHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//把數據寫到數據庫中,並且不再調用NextHandler
return true;
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
然後由調用者組裝:
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("==開始創建商品==");
IItemCreateHandler handler1 = new ItemInfoEntryHandler();
IItemCreateHandler handler2 = new ItemCreateApproveHandler();
IItemCreateHandler handler3 = new ItemInfoPersistenceHandler();
handler1.SetNextHandler(handler2);
handler2.SetNextHandler(handler3);
bool createSusess = false;
while (!createSusess)
{
Console.Write("請輸入商品名:");
string itemName = Console.ReadLine();
createSusess = handler1.HandleItemCreate(new ItemInfo() { Name = itemName });
}
Console.WriteLine("==商品創建結束==");
Console.ReadLine();
}
}
至此,我們的第一階段已經設計完成。
應對增加處理環節
上面我們看到,業務變化後還增加了一個映射環節。只需要加一個類,並且簡單修改下調用處的代碼即可搞定:
增加映射代碼:
/// <summary>
/// 商品信息映射處理者
/// </summary>
internal class ItemInfoMapHandler : IItemCreateHandler
{
public IItemCreateHandler NextHandler { get; protected set; }
public bool HandleItemCreate(ItemInfo itemInfo)
{
//在這個類中記錄下子公司與分司的關係
return this.NextHandler.HandleItemCreate(itemInfo);
}
public void SetNextHandler(IItemCreateHandler handler)
{
this.NextHandler = handler;
}
}
修改調用代碼:
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("==開始創建商品==");
IItemCreateHandler handler1 = new ItemInfoEntryHandler();
IItemCreateHandler handler2 = new ItemCreateApproveHandler();
IItemCreateHandler handler3 = new ItemInfoPersistenceHandler();
IItemCreateHandler newHandler = new ItemInfoMapHandler();//新加代碼
handler1.SetNextHandler(newHandler);//修改代碼
newHandler.SetNextHandler(handler2);//修改代碼
handler2.SetNextHandler(handler3);
bool createSusess = false;
while (!createSusess)
{
Console.Write("請輸入商品名:");
string itemName = Console.ReadLine();
createSusess = handler1.HandleItemCreate(new ItemInfo() { Name = itemName });
}
Console.WriteLine("==商品創建結束==");
Console.ReadLine();
}
}
優缺點分析
代碼寫了一些了,我們來分析下目前的優點和缺點吧!
優點:
- 通過代碼能很好的看出我們的業務模型。
- 代碼耦合度低
缺點:
- 需要使用者自己組裝並調用,不清真。
- 中間環節出現異常時,可以中斷時還好。但如果是返回到非起始點,怎麼辦呢?
針對缺點中的第1點,可以使用一個幫助類或簡單工廠封裝一下就好了。
針對第2點,當A環節需要跳到B環節時,這裏的對象也只有2個。 那麼我們這裏可以在A環節的代碼中手動new一個B環節的代碼來搞定。如果你覺得這種比較Low,也可以使用其它方式,核心就在於A環節調用B環節而已。
結合建造者模式
上面說了這麼多,都是在講責任鏈模式和它的應用,在某些層面可以與建造者模式很好的結合。
看下面的分析:
發現有3個固定步驟:
- 獲取數據
- 檢查
- 持久化 ,並且順序也是固定的。
這是不是可以改造成建造者模式呢? 但現在的這個業務不一定會改造成建造者模式,因爲需求變化還不足以讓我們添加一個新的模式進來!
但這種場景就適合了:
- 針對不同的商品、有不同的審批環節。比如:普通的商品由操作員查看、虛擬類商品由系統查看賣家的資格自動審批、奢侈品需要主管審批等等。
- 獲取數據的來源有多種。比如:從文件中批量獲取、用爬蟲在網絡上爬、UI上手動輸入等等
- 持久化到其它的數據庫
- 其它…
不管場景怎麼變,但我們的核心業務模型沒有變化,只有具體的實現變了。當你有這種需求的時候,就可以結合建造者模式去快速應對需求變化了。
如果你想詳細瞭解建造者模式,可去系列教程目錄查找。
總結
責任鏈模式強調:環節或模塊之間的順序調用關係。.net core中的中間件思想、http處理管道都可以看成這種思想的一種延伸。
建造者模式強調:執行的代碼是固定順序,固定步驟,但實現的細節多變。工廠模式就可以看成一個單步驟的建造者模式。