天天看點

C#設計模式-責任鍊模式(Chain of Responsibility Pattern)

引子

一個事件需要經過多個對象處理是一個挺常見的場景,譬如采購審批流程,請假流程,軟體開發中的異常處理流程,web請求處理流程等各種各樣的流程,可以考慮使用責任鍊模式來實作。

現在以請假流程為例,一般公司普通員工的請假流程簡化如下:

普通員工發起一個請假申請,當請假天數小于3天時隻需要得到主管準許即可;當請假天數大于3天時,主管準許後還需要送出給經理審批,經理審批通過,若請假天數大于7天還需要進一步送出給總經理審批。

C#設計模式-責任鍊模式(Chain of Responsibility Pattern)

簡單的流程可以通過 if-else 即可實作:

public class Leave
    {
        public void leaveApproval(int leaveDays)
        {
            if (leaveDays < 3)
            {
                Console.WriteLine("項目經理審批");
            }
            else if (leaveDays < 7)
            {
                Console.WriteLine("部門經理審批");
            }
            else if (leaveDays < 30)
            {
                Console.WriteLine("總經理審批");
            }
            else
            {
                Console.WriteLine("審批困難");
            }
        }
    }      

但是這樣的寫法看起來簡單,後續維護難度卻是不少。可以看出代碼臃腫且耦合度高。

  • 代碼臃腫:實際應用中的判定條件通常不是這麼簡單地判斷,也許需要複雜的計算,也許需要查詢資料庫等等,這就會有很多額外的代碼,如果判斷條件再比較多,那麼這個if…else…語句基本上就沒法看了。
  • 耦合度高:如果我們想繼續添加處理請求的類,那麼就要繼續添加else if判定條件;另外,這個條件判定的順序也是寫死的,如果想改變順序,那麼也隻能修改這個條件語句。

在設計模式中提倡單一職責原則,如果項目組内再加一個組長,審批請假小于一天的呢?此時就會感覺 if-else 靈活性太差,修改代碼後測試需要重新測試全部流程才能保證品質。

既然已經清楚他的不足,則針對此業務邏輯可以稍作轉換:如果滿足條件1,則由 Handler1 來處理,不滿足則向下傳遞;如果滿足條件2,則由 Handler2 來處理,不滿足則繼續向下傳遞,以此類推,直到條件結束。其實改進的方法也很簡單,就是把判定條件的部分放到處理類中,這就是責任連模式的原理。

定義

責任鍊模式屬于行為類模式。使多個對象都有機會處理請求,進而避免了請求的發送者和接收者之間的耦合關系。将這些對象連成一條鍊,并沿着這條鍊傳遞該請求,直到有對象處理它為止。

責任鍊模式把多個處理器串成鍊,然後讓請求在鍊上傳遞:

C#設計模式-責任鍊模式(Chain of Responsibility Pattern)

類圖

從的定義可以看出涉及的對象隻有處理者角色,但可以有多個處理者,這些處理者做的事情都是一樣的,處理請求的方法,是以可以抽象出一個處理者角色進行代碼複用。如下類圖。

C#設計模式-責任鍊模式(Chain of Responsibility Pattern)

角色

  • Handler(抽象處理類):抽象處理類中主要包含一個指向下一處理類的成員變量nextHandler和一個處理請求的方法handRequest,handRequest方法的主要主要思想是,如果滿足處理的條件,則有本處理類來進行處理,否則由nextHandler來處理。
  • ConcreteHandler(具體處理類):具體處理類主要是對具體的處理邏輯和處理的适用條件進行實作。

實作

将上面的請假流程重新梳理,使用責任鍊模式進行實作:

using System;

namespace 責任鍊模式
{
    class Program
    {
        static void Main(string[] args)
        {
            LeaveRequest leaveTwoDays = new LeaveRequest(2, "grey1");
            LeaveRequest leaveSixDays = new LeaveRequest(6, "grey2");
            LeaveRequest leaveEightDays = new LeaveRequest(8, "grey3");

            Approver PM = new Manager("jon1");
            Approver DM = new DepartmentManager("jon2");
            Approver GM = new GeneralManager("jon3");

            // 設定責任鍊
            PM.NextApprover = DM;
            DM.NextApprover = GM;

            // 處理請求
            PM.LeaveRequest(leaveTwoDays);
            PM.LeaveRequest(leaveSixDays);
            PM.LeaveRequest(leaveEightDays);
            Console.ReadLine();
        }
    }

        
    // 請假需求
    public class LeaveRequest
    {
        public int Day { get; set; }

        public string Name { get; set; }

        public LeaveRequest(int day, string name)
        {
            this.Day = day;
            this.Name = name;
        }
    }

    // 審批人
    public abstract class Approver {
        public Approver NextApprover { get; set; }
        public string Name { get; set; }

        public Approver(string name)
        {
            this.Name = name;
        }

        public abstract void LeaveRequest(LeaveRequest requeset);
    }


    // 項目經理
    public class Manager : Approver
    {
        public Manager(string name)
            : base(name) { }

        public override void LeaveRequest(LeaveRequest requeset)
        {
            if (requeset.Day < 3)
            {
                Console.WriteLine("項目經理 {0} 審批 {1} 請假", this.Name, requeset.Name);
            }
            else
            {
                NextApprover.LeaveRequest(requeset);
            }
        }
    }


    // 部門經理
    public class DepartmentManager : Approver
    {
        public DepartmentManager(string name)
            : base(name) { }

        public override void LeaveRequest(LeaveRequest requeset)
        {
            if (requeset.Day < 7)
            {
                Console.WriteLine("部門經理 {0} 審批 {1} 請假", this.Name, requeset.Name);
            }
            else
            {
                NextApprover.LeaveRequest(requeset);
            }
        }
    }

    // 總經理
    public class GeneralManager : Approver
    {
        public GeneralManager(string name)
            : base(name) { }

        public override void LeaveRequest(LeaveRequest requeset)
        {
            if (requeset.Day < 30)
            {
                Console.WriteLine("總經理 {0} 審批 {1} 請假", this.Name, requeset.Name);
            }
            else
            {
                Console.WriteLine("審批困難"); ;
            }
        }
    }
}      

運作一下:  

項目經理 jon1 審批 grey1 請假
部門經理 jon2 審批 grey2 請假
總經理 jon3 審批 grey3 請假      

LeaveRequest 類為請求請假。

Approver 為處理人員。并且設定了三個處理人員,Manager、DepartmentManager、GeneralManager。

設定的責任鍊為 Manager-->DepartmentManager-->GeneralManager。當發生請假請求時首先由Manager進行處理,處理不了轉由DepartmentManager,如果DepartmentManager還是處理不了則繼續向更好職位的人員GeneralManager進行送出,由更大權限的人進行處理。

實作的功能和文章最初的 if...else 一樣。但時可以看到使用責任鍊模式代碼更清楚,請求發送者是發送者,接收者是接收者。

适用場景 

通過上面的定義、類圖及示例可以考慮責任鍊模式适用的場景:

  • 在不明确指定請求處理者的情況下,向多個處理者中的一個送出請求。
  • 代碼中存在多個if-else語句的情況下,此時可以考慮使用責任鍊模式來對代碼進行重構。
  • 可動态指定一組對象處理請求,用戶端可以動态建立職責鍊來處理請求,還可以改變鍊中處理者之間的先後次序。

優缺點

通過上面的介紹很容易發現,責任鍊模式的優點:

  • 降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鍊的結構,發送者和接收者也無須擁有對方的明确資訊。
  • 增強了系統的可擴充性。可以根據需要增加新的請求處理類,滿足開閉原則。
  • 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動态地改變鍊内的成員或者調動它們的次序,也可動态地新增或者删除責任。
  • 責任鍊簡化了對象之間的連接配接。每個對象隻需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。
  • 責任分擔。每個類隻需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明确各類的責任範圍,符合類的單一職責原則。

但也有缺點:

  • 不能保證每個請求一定被處理。由于一個請求沒有明确的接收者,是以不能保證它一定會被處理,該請求可能一直傳到鍊的末端都得不到處理。
  • 對比較長的職責鍊,請求的處理可能涉及多個處理對象,系統性能将受到一定影響。
  • 職責鍊建立的合理性要靠用戶端來保證,增加了用戶端的複雜性,可能會由于職責鍊的錯誤設定而導緻系統出錯,如可能會造成循環調用。

擴充

純的責任鍊模式:

  • 一個具體處理者對象隻能在兩個行為中選擇一個:要麼承擔全部責任,要麼将責任推給下家,不允許出現某一個具體處理者對象在承擔了一部分或全部責任後 又将責任向下傳遞的情況。
  • 一個請求必須被某一個處理者對象所接收,不能出現某個請求未被任何一個處理者對象處理的情況。

不純的責任鍊模式:

  • 允許某個請求被一個具體處理者部分處理後再向下傳遞。
  • 或者一個具體處理者處理完某請求後其後繼處理者可以繼續處理該請求。
  • 而且一個請求可以最終不被任何處理者對象所接收。

總結

責任鍊模式其實就是一個靈活版的if…else…語句,将這些判定條件的語句放到了各個處理類中,非常靈活。

責任鍊模式是一種把多個處理器組合在一起,依次處理請求的模式。

責任鍊降低了請求端和接收端之間的耦合,使多個對象都有機會處理某個請求。

責任鍊模式經常用在攔截、預處理請求等。

與此同樣也帶來了風險,比如設定處理類前後關系時,一定要特别仔細,搞對處理類前後邏輯的條件判斷關系,并且注意不要在鍊中出現循環引用的問題。

參考

https://juejin.im/post/6844903702260629512

https://www.w3cschool.cn/javadesignpattern/omas1ii2.html

https://www.cnblogs.com/zhili/p/ChainOfResponsibity.html

http://c.biancheng.net/view/1383.html

https://www.liaoxuefeng.com/wiki/1252599548343744/1281319474561057

繼續閱讀