換個角度理解設計模式之責任鏈模式

系列文章的目錄:https://blog.csdn.net/hjkl950217/article/details/89490709

記住:設計模式重理解輕照搬

業務背景

想象一下你在做一個簡單的電商系統,電商系統中核心一定會有商品訂單這2個東西,而商品必然會有數量、品牌、商品描述信息這些方面。而商品上架之後纔會有訂單這個東西,那麼我們第一件事就是進行商品管理。一個簡單的商品創建模型如下:
在這裏插入圖片描述

隨着業務變化,你發現商品的不同品牌之間有着關聯。比如AMD(中國)和AMD(美國)可以看做一個公司,但在具體商品上又要區分開,記錄上主公司與分公司的關係。那麼我們的模型發生了一點變化:

在這裏插入圖片描述

設計

在開始之前,建議先停下來想想,如果是你,你會如何設計? 然後再向下看。

觀察上面整理出來的業務模型發現特點:一環扣一環,審批處如果審批不過會退回到起始點

我們這裏簡單對比下傳統思路與責任鏈模式:

  1. 如果用傳統的業務思想去開發:可能會是一個方法中調用不同的子方法,然後判斷每個子方法的返回結果並做出不同的處理方式。隨着業務變化,這個方法會變的越來越龐大,修改一處都有可能影響整套代碼
  2. 如果使用責任鏈模式:把每一環拆開進行開發,然後在使用處組裝成我們的業務處理鏈。這樣在開發時我們並不關心整體如何,而是我們這一處地方應該如何。並且業務變化時,中斷整體調用,由具體的環節給出對應的方案。

下面是實現的代碼,你也可以下載查看 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);
    }

實現的代碼就很簡單了:

  1. 處理錄入的信息
  2. 審批
  3. 持久化到數據庫
    代碼:
    /// <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. 代碼耦合度低

缺點

  1. 需要使用者自己組裝並調用,不清真。
  2. 中間環節出現異常時,可以中斷時還好。但如果是返回到非起始點,怎麼辦呢?

針對缺點中的第1點,可以使用一個幫助類或簡單工廠封裝一下就好了。
針對第2點,當A環節需要跳到B環節時,這裏的對象也只有2個。 那麼我們這裏可以在A環節的代碼中手動new一個B環節的代碼來搞定。如果你覺得這種比較Low,也可以使用其它方式,核心就在於A環節調用B環節而已。

結合建造者模式

上面說了這麼多,都是在講責任鏈模式和它的應用,在某些層面可以與建造者模式很好的結合。

看下面的分析:
在這裏插入圖片描述

發現有3個固定步驟:

  1. 獲取數據
  2. 檢查
  3. 持久化 ,並且順序也是固定的。

這是不是可以改造成建造者模式呢? 但現在的這個業務不一定會改造成建造者模式,因爲需求變化還不足以讓我們添加一個新的模式進來!

但這種場景就適合了:

  1. 針對不同的商品、有不同的審批環節。比如:普通的商品由操作員查看、虛擬類商品由系統查看賣家的資格自動審批、奢侈品需要主管審批等等。
  2. 獲取數據的來源有多種。比如:從文件中批量獲取、用爬蟲在網絡上爬、UI上手動輸入等等
  3. 持久化到其它的數據庫
  4. 其它…

不管場景怎麼變,但我們的核心業務模型沒有變化,只有具體的實現變了。當你有這種需求的時候,就可以結合建造者模式去快速應對需求變化了。

如果你想詳細瞭解建造者模式,可去系列教程目錄查找。

總結

責任鏈模式強調:環節或模塊之間的順序調用關係。.net core中的中間件思想、http處理管道都可以看成這種思想的一種延伸。
建造者模式強調:執行的代碼是固定順序固定步驟,但實現的細節多變。工廠模式就可以看成一個單步驟的建造者模式。


  1. 責任鏈模式.7z ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章