相信大家都玩過(guò)類似于“斗地主”的紙牌游戲,某人出牌給他的下家,下家看看手中的牌,如果要不起,則將出牌請(qǐng)求轉(zhuǎn)發(fā)給他的下家,其下家再進(jìn)行判斷。一個(gè)循環(huán)下來(lái),如果其他人都要不起該牌,則最初的出牌者可以打出新牌。在這個(gè)過(guò)程中,紙牌作為一個(gè)請(qǐng)求沿著一條鏈在傳遞,每一位紙牌的玩家都可以處理該請(qǐng)求。在設(shè)計(jì)模式中,也有一種專門用于處理這種請(qǐng)求鏈?zhǔn)降哪J?,它就是職?zé)鏈模式。

職責(zé)鏈模式(Chain of Responsibility)學(xué)習(xí)難度:★★★☆☆使用頻率:★★☆☆☆

一、采購(gòu)單的分級(jí)審批模塊設(shè)計(jì)

需求背景:M公司承接了某企業(yè)SCM(Supply Chain Management,供應(yīng)鏈管理)系統(tǒng)的開(kāi)發(fā)任務(wù),其中包含一個(gè)采購(gòu)審批子系統(tǒng)。該企業(yè)的采購(gòu)審批是分級(jí)進(jìn)行的,即根據(jù)采購(gòu)金額的不同由不同層次的主管人員來(lái)審批:主任可以審批5萬(wàn)元以下(不包括5萬(wàn))的采購(gòu)單,副董事長(zhǎng)可以審批5萬(wàn)~10萬(wàn)(不包括10萬(wàn))的采購(gòu)單,50萬(wàn)元以及以上的采購(gòu)單就需要開(kāi)董事會(huì)討論決定,如下圖所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  M公司開(kāi)發(fā)人員提出了一個(gè)初始解決方案,提供了一個(gè)采購(gòu)單處理類PurchaseRequestHandler用于統(tǒng)一處理采購(gòu)單,其框架代碼如下:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    /// <summary>
    /// 采購(gòu)單處理類    /// </summary>
    public class PurchaseRequestHandler
    {        // 遞交采購(gòu)單給審批者
        public void SendRequestToApprover(PurchaseRequest request)
        {            if (request.Amount < 5000) // 主任可審批該采購(gòu)單            {
                HandleByDirector(request);
            }            else if(request.Amount < 100000) // 副董事長(zhǎng)可審批該采購(gòu)單            {
                HandleByVicePresident(request);
            }            else if (request.Amount < 500000) // 董事長(zhǎng)可審批該采購(gòu)單            {
                HandleByPresident(request);
            }            else
            {
                HandleByCongress(request); // 董事會(huì)可審批該采購(gòu)單            }
        }        // 主管審批采購(gòu)單
        private void HandleByDirector(PurchaseRequest request)
        {            // 代碼省略        }        // 副董事長(zhǎng)審批采購(gòu)單
        private void HandleByVicePresident(PurchaseRequest request)
        {            // 代碼省略        }        // 董事長(zhǎng)審批采購(gòu)單
        private void HandleByPresident(PurchaseRequest request)
        {            // 代碼省略        }        // 董事會(huì)審批采購(gòu)單
        private void HandleByCongress(PurchaseRequest request)
        {            // 代碼省略        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  不過(guò)仔細(xì)分析后發(fā)現(xiàn),上述方案存在以下3個(gè)問(wèn)題:

 ?。?)PurchaseRequestHandler類較為龐大,各個(gè)級(jí)別的審批方法都集中在一個(gè)類中,違反了單一職責(zé)原則,測(cè)試和維護(hù)難度較大。

 ?。?)如果需要新增一個(gè)新的審批級(jí)別或調(diào)整任何一級(jí)的審批金額和審批細(xì)節(jié)時(shí)都必須修改源代碼并進(jìn)行嚴(yán)格測(cè)試。此外,如果需要移除某一級(jí)別時(shí)也需要對(duì)源代碼進(jìn)行修改,違反了開(kāi)閉原則。

 ?。?)審批流程的設(shè)置缺乏靈活性,現(xiàn)在的審批流程是“主任->副董事長(zhǎng)->董事長(zhǎng)->董事會(huì)”,如果需要改為“主任->董事長(zhǎng)->董事會(huì)”,在此方案中只能通過(guò)修改源代碼來(lái)實(shí)現(xiàn),客戶端無(wú)法定制審批流程。

  那么如何破呢?別急,來(lái)看看職責(zé)鏈模式。

二、職責(zé)鏈模式概述

2.1 職責(zé)鏈模式簡(jiǎn)介

職責(zé)鏈(Chain of Responsibility)模式:避免將請(qǐng)求發(fā)送者與接受者耦合在一起,讓多個(gè)對(duì)象都有機(jī)會(huì)接受請(qǐng)求,將這些對(duì)象連成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。職責(zé)鏈模式是一種對(duì)象行為型模式。  

2.2 職責(zé)鏈模式結(jié)構(gòu)

  職責(zé)鏈模式結(jié)構(gòu)的核心就在于引入了一個(gè)抽象處理者,其結(jié)構(gòu)如下圖所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  在職責(zé)鏈模式結(jié)構(gòu)圖中包含以下兩個(gè)角色:

  (1)Handler(抽象處理者):定義了一個(gè)處理請(qǐng)求的接口,一般設(shè)計(jì)為抽象類,由于不同的具體處理者處理請(qǐng)求的方式不同,因此在其中定義了抽象請(qǐng)求處理方法。

  (2)ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理用戶請(qǐng)求,它實(shí)現(xiàn)了在抽象處理者中定義的抽象請(qǐng)求處理方法。在處理請(qǐng)求之前需要判斷是否有相應(yīng)的處理權(quán)限,如果可以則處理,否則則將請(qǐng)求轉(zhuǎn)發(fā)給后繼者。

三、重構(gòu)采購(gòu)單分級(jí)審批模塊

3.1 重構(gòu)后的設(shè)計(jì)

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  其中,抽象類Approver充當(dāng)抽象處理類,Director, VicePresident, President以及Congress 充當(dāng)具體處理者,PurchaseRequest充當(dāng)請(qǐng)求類。

3.2 具體代碼實(shí)現(xiàn)

 ?。?)請(qǐng)求類:PurchaseRequest

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    /// <summary>
    /// 采購(gòu)單:請(qǐng)求類    /// </summary>
    public class PurchaseRequest
    {        // 采購(gòu)金額
        public double Amount { get; set; }        // 采購(gòu)單編號(hào)
        public string Number { get; set; }        // 采購(gòu)目的
        public string Purpose { get; set; }        public PurchaseRequest(double amount, string number, string purpose)
        {
            Amount = amount;
            Number = number;
            Purpose = purpose;
        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  (2)抽象處理者:Approver

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    /// <summary>
    /// 審批者類:抽象處理者    /// </summary>
    public abstract class Approver
    {        protected Approver successor; // 定義后繼對(duì)象
        protected string name;  // 審批者姓名

        public Approver(string name)
        {            this.name = name;
        }        // 設(shè)置后繼者
        public void SetSuccessor(Approver successor)
        {            this.successor = successor;
        }        // 抽象請(qǐng)求處理方法
        public abstract void ProcessRequest(PurchaseRequest request);
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 ?。?)具體處理者:Director, VicePresident, President以及Congress 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    /// <summary>
    /// 總監(jiān):具體處理類    /// </summary>
    public class Director : Approver
    {        public Director(string name) : base(name)
        {
        }        // 具體請(qǐng)求處理方法
        public override void ProcessRequest(PurchaseRequest request)
        {            if (request.Amount < 50000)
            {                // 處理請(qǐng)求
                Console.WriteLine("主管 {0} 審批采購(gòu)單:{1},金額:{2} 元,采購(gòu)目的:{3}。",                    this.name, request.Number, request.Amount, request.Purpose);
            }            else
            {                // 如果處理不了,轉(zhuǎn)發(fā)請(qǐng)求給更高層領(lǐng)導(dǎo)
                this.successor.ProcessRequest(request);
            }
        }
    }    /// <summary>
    /// 副總裁:具體處理類    /// </summary>
    public class VicePresident : Approver
    {        public VicePresident(string name) : base(name)
        {
        }        // 具體請(qǐng)求處理方法
        public override void ProcessRequest(PurchaseRequest request)
        {            if (request.Amount < 100000)
            {                // 處理請(qǐng)求
                Console.WriteLine("副總裁 {0} 審批采購(gòu)單:{1},金額:{2} 元,采購(gòu)目的:{3}。",                    this.name, request.Number, request.Amount, request.Purpose);
            }            else
            {                // 如果處理不了,轉(zhuǎn)發(fā)請(qǐng)求給更高層領(lǐng)導(dǎo)
                this.successor.ProcessRequest(request);
            }
        }
    }    /// <summary>
    /// 總裁:具體處理者    /// </summary>
    public class President : Approver
    {        public President(string name) : base(name)
        {
        }        // 具體請(qǐng)求處理方法
        public override void ProcessRequest(PurchaseRequest request)
        {            if (request.Amount < 500000)
            {                // 處理請(qǐng)求
                Console.WriteLine("總裁 {0} 審批采購(gòu)單:{1},金額:{2} 元,采購(gòu)目的:{3}。",                    this.name, request.Number, request.Amount, request.Purpose);
            }            else
            {                // 如果處理不了,轉(zhuǎn)發(fā)請(qǐng)求給更高層領(lǐng)導(dǎo)
                this.successor.ProcessRequest(request);
            }
        }
    }    /// <summary>
    /// 董事會(huì):具體處理者    /// </summary>
    public class Congress : Approver
    {        public Congress(string name) : base(name)
        {
        }        // 具體請(qǐng)求處理方法
        public override void ProcessRequest(PurchaseRequest request)
        {            // 處理請(qǐng)求
            Console.WriteLine("董事會(huì) {0} 審批采購(gòu)單:{1},金額:{2} 元,采購(gòu)目的:{3}。",                this.name, request.Number, request.Amount, request.Purpose);
        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 ?。?)客戶端測(cè)試:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    public class Program
    {        public static void Main(string[] args)
        {            // 創(chuàng)建職責(zé)鏈
            Approver andy = new Director("Andy");
            Approver jacky = new VicePresident("Jacky");
            Approver ashin = new President("Ashin");
            Approver meeting = new Congress("Congress");

            andy.SetSuccessor(jacky);
            jacky.SetSuccessor(ashin);
            ashin.SetSuccessor(meeting);            // 構(gòu)造采購(gòu)請(qǐng)求單并發(fā)送審批請(qǐng)求
            PurchaseRequest request1 = new PurchaseRequest(45000.00,                "MANULIFE201706001",                "購(gòu)買PC和顯示器");
            andy.ProcessRequest(request1);

            PurchaseRequest request2 = new PurchaseRequest(60000.00,                "MANULIFE201706002",                "2017開(kāi)發(fā)團(tuán)隊(duì)活動(dòng)");
            andy.ProcessRequest(request2);

            PurchaseRequest request3 = new PurchaseRequest(160000.00,                "MANULIFE201706003",                "2017公司年度旅游");
            andy.ProcessRequest(request3);

            PurchaseRequest request4 = new PurchaseRequest(800000.00,                "MANULIFE201706004",                "租用新臨時(shí)辦公樓");
            andy.ProcessRequest(request4);

            Console.ReadKey();
        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  編譯運(yùn)行后的結(jié)果如下圖所示:

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

3.3 需求擴(kuò)展實(shí)現(xiàn)

  這時(shí),假設(shè)需要在系統(tǒng)中新增一個(gè)新的具體處理者,例如增加一個(gè)經(jīng)理(Manager)角色可以審批5萬(wàn)~8萬(wàn)(不包括8萬(wàn))的采購(gòu)單。因此,我們可以新增一個(gè)具體處理者:Manager

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    /// <summary>
    /// 經(jīng)理:具體處理者    /// </summary>
    public class Manager : Approver
    {        public Manager(string name) : base(name)
        {
        }        // 具體請(qǐng)求處理方法
        public override void ProcessRequest(PurchaseRequest request)
        {            if (request.Amount < 80000)
            {                // 處理請(qǐng)求
                Console.WriteLine("經(jīng)理 {0} 審批采購(gòu)單:{1},金額:{2} 元,采購(gòu)目的:{3}。",                    this.name, request.Number, request.Amount, request.Purpose);
            }            else
            {                this.successor.ProcessRequest(request);
            }
        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  由于鏈的創(chuàng)建過(guò)程由客戶端負(fù)責(zé),因此此擴(kuò)展對(duì)原有類庫(kù)無(wú)任何影響,符合開(kāi)閉原則。而我們需要做的,僅僅是在客戶端代碼中新增職責(zé)鏈關(guān)系的創(chuàng)建即可。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

    public class Program
    {        public static void Main(string[] args)
        {            // 創(chuàng)建職責(zé)鏈
            Approver andy = new Director("Andy");
            Approver jacky = new Manager("Jacky");
            Approver ashin = new VicePresident("Ashin");
            Approver anya = new President("Anya");
            Approver meeting = new Congress("Congress");

            andy.SetSuccessor(jacky);
            jacky.SetSuccessor(ashin);
            ashin.SetSuccessor(anya);
            anya.SetSuccessor(meeting);            // 構(gòu)造采購(gòu)請(qǐng)求單并發(fā)送審批請(qǐng)求
            PurchaseRequest request1 = new PurchaseRequest(45000.00,                "MANULIFE201706001",                "購(gòu)買PC和顯示器");
            andy.ProcessRequest(request1);

            PurchaseRequest request2 = new PurchaseRequest(60000.00,                "MANULIFE201706002",                "2017開(kāi)發(fā)團(tuán)隊(duì)活動(dòng)");
            andy.ProcessRequest(request2);

            PurchaseRequest request3 = new PurchaseRequest(160000.00,                "MANULIFE201706003",                "2017公司年度旅游");
            andy.ProcessRequest(request3);

            PurchaseRequest request4 = new PurchaseRequest(800000.00,                "MANULIFE201706004",                "租用新臨時(shí)辦公樓");
            andy.ProcessRequest(request4);

            Console.ReadKey();
        }
    }

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  重新編譯運(yùn)行后的結(jié)果如下圖所示:

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

四、職責(zé)鏈模式總結(jié)

4.1 主要優(yōu)點(diǎn)

 ?。?)使得一個(gè)對(duì)象無(wú)需知道是其他哪一個(gè)對(duì)象處理其請(qǐng)求,對(duì)象僅需知道該請(qǐng)求會(huì)被處理即可,且鏈?zhǔn)浇Y(jié)構(gòu)由客戶端創(chuàng)建 => 降低了系統(tǒng)的耦合度

 ?。?)在系統(tǒng)中增加一個(gè)新的具體處理者無(wú)須修改原有系統(tǒng)源代碼,只需要在客戶端重新建立鏈?zhǔn)浇Y(jié)構(gòu)即可 => 符合開(kāi)閉原則

4.2 主要缺點(diǎn)

  (1)由于一個(gè)請(qǐng)求沒(méi)有一個(gè)明確地接受者 => 無(wú)法保證它一定會(huì)被處理

 ?。?)對(duì)于較長(zhǎng)的職責(zé)鏈 => 系統(tǒng)性能有一定影響且不利于調(diào)試

  (3)如果建立鏈不當(dāng),可能會(huì)造成循環(huán)調(diào)用 => 導(dǎo)致系統(tǒng)進(jìn)入死循環(huán)

4.3 應(yīng)用場(chǎng)景

  (1)有多個(gè)對(duì)象處理同一個(gè)請(qǐng)求且無(wú)需關(guān)心請(qǐng)求的處理對(duì)象時(shí)誰(shuí)以及它是如何處理的 => 比如各種審批流程

 ?。?)可以動(dòng)態(tài)地指定一組對(duì)象處理請(qǐng)求,客戶端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來(lái)處理請(qǐng)求,還可以改變鏈中處理者之間的先后次序 => 比如各種流程定制

參考資料

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  劉偉,《設(shè)計(jì)模式的藝術(shù)—軟件開(kāi)發(fā)人員內(nèi)功修煉之道》

 

作者:周旭龍

出處:http://edisonchou.cnblogs.com

本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文鏈接。

http://www.cnblogs.com/edisonchou/p/7215547.html