權力越大職責越大——C#中的職責鏈模式

大家好,歡迎來到老胡的博客,今天我們繼續瞭解設計模式中的職責鏈模式,這是一個比較簡單的模式。跟往常一樣,我們還是從一個真實世界的例子入手,這樣大家也對這個模式的應用場景有更深刻的理解。
 

一個真實的栗子

作爲上班族,相信大家對請假都不陌生,每個公司都有自己請假的流程,稍微講究點的公司還會有細緻的規定,比如,3天以內的假期,小組長有權力批准,3天以上的假期就要找更高級別的領導批准。這種制度就是典型的權力越大職責越大——畢竟,批長假的職責只在高級主管那裏存在。

除了規定出這樣細緻的要求之外,大部分公司還有用軟件實現了請假流程,當請假人員提出請假申請的時候,會依據請假天數,轉發給具有權限的人員審批,讓我們看看這個系統的代碼實現吧。
 

請假系統實現

在這個系統中,我們假定:

  • 小組長可以審批3天以內的請假請求
  • 部門經理可以審批5天以內的請假請求
  • 10天以內的請假請求只有老闆才能審批
  • 我們同時假定,這個公司的管理層非常人性化,請假都能得到批准,除非大於10天,因爲這種情況沒人可以審批
     
請假申請

這是最簡單的類,封裝了請假天數和請假申請人

class VacationRequest
{
    public int DayNum { get; set; }
    public string RequesterName { get; set; }
}

 

假期審批者

首先創建一個抽象類,假期審批者,封裝假期審批的基本邏輯,即,如果當前人員有權限審批當前假期申請,就處理

abstract class VacationApprover
{        
    protected VacationApprover(int dayCanHandle)
    {
        DayCanHandle = dayCanHandle;
    }

    public int DayCanHandle { get; protected set; }

    public void HandleVacationRequest(VacationRequest request)
    {
        if (request.DayNum <= DayCanHandle)
        {
            DoHandleVacationRequest(request);
        }
    }

    protected abstract void DoHandleVacationRequest(VacationRequest request);
}

當然,抽象類只需要確定算法骨架,限定只有當前人員能處理這個請求的時候,才進行審批工作,至於具體的審批實現,留給子類自己去覆蓋,這種在父類固定算法骨架,暴露部分覆蓋點給子類的做法,就是之前我們提到過的TemplateMethod模式
 

具體假期審批者

小組長,部門經理,老闆,都在這裏創建,他們分別處理能審批3、5、10天的請假申請

class TeamLeader : VacationApprover
{
    private const int DAY_CAN_HANDLE_TEAMLEADER = 3;
    public TeamLeader() : base(DAY_CAN_HANDLE_TEAMLEADER) { }

    protected override void DoHandleVacationRequest(VacationRequest request)
    {
        Console.WriteLine("Now team leader handle this request");
        Console.WriteLine("Team leader accept this request");
    }
}

class DepartmentLeader : VacationApprover
{
    private const int DAY_CAN_HANDLE_DEPARTMENTLEADER = 5;
    public DepartmentLeader() : base(DAY_CAN_HANDLE_DEPARTMENTLEADER) { }

    protected override void DoHandleVacationRequest(VacationRequest request)
    {
        Console.WriteLine("Now department leader handle this request");
        Console.WriteLine("Department leader accept this request");
    }
}

class Boss : VacationApprover
{
    private const int DAY_CAN_HANDLE_BOSS = 10;
    public Boss() : base(DAY_CAN_HANDLE_BOSS) { }

    protected override void DoHandleVacationRequest(VacationRequest request)
    {
        Console.WriteLine("Now boss handle this request");
        Console.WriteLine("Boss accept this request");
    }
}

 

請假審批系統

請假審批系統提供統一請假申請接口,內部通過請假天數決定哪個審批者參與審批

class VacationApproveSystem
{
    private VacationApprover teamLeader = new TeamLeader();
    private VacationApprover departmentLeader = new DepartmentLeader();
    private VacationApprover boss = new Boss();

    public void HandleVacationRequest(VacationRequest request)
    {
        Console.WriteLine("Now handle {0}'s {1} days' vacation request", request.RequesterName, request.DayNum);

        if (request.DayNum <= teamLeader.DayCanHandle)
        {
            teamLeader.HandleVacationRequest(request);
        }
        else if (request.DayNum <= departmentLeader.DayCanHandle)
        {
            departmentLeader.HandleVacationRequest(request);
        }
        else if (request.DayNum <= boss.DayCanHandle)
        {
            boss.HandleVacationRequest(request);
        }
        else
        {
            Console.WriteLine("Cannot handle this request after all");
        }
    }
}

 

測試代碼
class Program
{
    static void Main(string[] args)
    {
        VacationApproveSystem system = new VacationApproveSystem();

        system.HandleVacationRequest(new VacationRequest() { DayNum = 5, RequesterName = "laohu" });

        system.HandleVacationRequest(new VacationRequest() { DayNum = 10, RequesterName = "laohu" });

        system.HandleVacationRequest(new VacationRequest() { DayNum = 12, RequesterName = "laohu" });
    }
}

結果顯示

一切都是正常的,當5天時,部門經理審批,10天時,老闆審批,大於10天無人能批。 Good job。
 

回頭看看

實現了第一版代碼之後,我們再回過頭看看,雖然代碼功能無誤,但是VacationApproveSystem似乎承擔了過多的職責,它不但需要提供統一的請假審批接口給最終用戶,它同時還需要知道每個請假審批者能審批的請假天數並在內部實現請假請求轉發給不同審批者的邏輯。這樣既違反了迪米特法則——它知道的太多了,也違反了開閉原則——如果任何一個審批者修改了自身能審批的請假天數,這個類都會被波及,最後,它還違反了單一職責——一個類只能有一個引起變化的原因。

有鑑於此,我們這版代碼只能算湊合用,但遠遠談不上結構良好,老老實實地重構代碼吧,下面請出我們今天的主角。
 

職責鏈模式

解耦具體對象和請求,使得多個對象都有機會處理請求。將對象連成一條鏈,沿着鏈傳遞請求直到有對象處理它

乍一聽有點生澀,翻譯一下就是

  • 解耦具體對象和請求——不要預先指定哪個對象來處理此請求(因爲很多時候並不知道)
  • 使多個對象都有機會——有一衆候選對象,具體使用哪個對象是在運行時決定的
  • 連成鏈傳遞請求——像鏈表一樣,要在對象中體現出對象之間的鏈關係,而不要通過其他類以if..else的方式實現

所以,這麼看來這個模式和我們的例子簡直是絕配,我們已經做了大部分的工作了,現在剩下的就只是修改審批者,讓審批者能起來
 

代碼重構

修改請假審批基類

最重要的改動,就是修改基類,讓對象能起來,在VacationApprover中添加一個後繼節點和一個設置後繼節點的方法。同時在基類的審批方法中,完成請求傳遞,即,如果請假申請超過了當前審批人的能力範圍,則轉發至後繼節點。修改後的類如下

abstract class VacationApprover
{
    private VacationApprover nextVacationApprover = null;

    public void SetNextVacationApprover(VacationApprover approver)
    {
        nextVacationApprover = approver;
    }

    protected VacationApprover(int dayCanHandle)
    {
        DayCanHandle = dayCanHandle;
    }

    public int DayCanHandle { get; protected set; }

    public void HandleVacationRequest(VacationRequest request)
    {
        if (request.DayNum <= DayCanHandle)
        {
            DoHandleVacationRequest(request);
        }
        else
        {
            if(nextVacationApprover != null)
            {
                nextVacationApprover.HandleVacationRequest(request);
            }
            else
            {
                Console.WriteLine("Cannot handle this request after all");
            }
        }
    }

    protected abstract void DoHandleVacationRequest(VacationRequest request);
}

 

修改請假審批系統

基類重構結束之後,請假審批系統就可以瘦身了,刪除了所有判斷邏輯,僅僅在構造函數裏面完成組建的工作,接着一鍵調用,齊活。

class VacationApproveSystem
{
    private VacationApprover teamLeader = new TeamLeader();
    private VacationApprover departmentLeader = new DepartmentLeader();
    private VacationApprover boss = new Boss();

    public VacationApproveSystem()
    {
        teamLeader.SetNextVacationApprover(departmentLeader);
        departmentLeader.SetNextVacationApprover(boss);
    }

    public void HandleVacationRequest(VacationRequest request)
    {
        Console.WriteLine("Now handle {0}'s {1} days' vacation request", request.RequesterName, request.DayNum);

        teamLeader.HandleVacationRequest(request);
    }
}

 

測試

其他請假審批子類和測試客戶端都不需要改動,這次重構工作量非常小,運行代碼,一切正常,重構成功。
 

總結

這就是職責鏈模式的使用。和狀態模式有點像,解決了以下問題:

  • 通過添加子類把一些邏輯判斷從調用類(VaccationApproveSystem)移到子類的方式,使得調用類滿足迪米特法則
  • 想在職責鏈上面添加更多節點的時候,只需要添加新類和修改鏈組裝部分的代碼,基本滿足開閉原則(這裏幾乎不可能完全滿足開閉原則,畢竟有修改就意味着我們肯定會改動VaccationApproveSystem類,只是我們應該儘量的讓代碼改動量少,以提高控制代碼變動的能力)

和狀態模式一樣,它也有子類爆炸的風險。

可能有朋友會感到疑惑,既然職責鏈模式和狀態模式看起來那麼像,那它們有什麼區別呢?它們的區別在於:

  • 狀態模式中的對象是有狀態的,可以隨時通過接口查詢對象的當前狀態,對象正是因爲有了不同的狀態,纔會表現出不同行爲。而職責鏈模式中的對象沒有狀態,對象和鏈的關係更像請求和處理管線的關係,沒有接口能告訴我們當前在處理管線的哪個節點,也沒有意義這麼做,我們只關心請求是否被處理了
  • 狀態模式中的狀態切換可以是無序的,比如,一個遊戲角色,當他的狀態是虛弱的時候,可以通過治療,轉換成健康,也可以通過受傷轉換成瀕死。而職責鏈中的請求轉發就只有向前一條路,從小組長到部門經理,從部門經理到老闆

根據不同的情景,選擇合適的模式,纔是正確的使用之道。以上就是今天的內容,希望大家喜歡,我們下次見!

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