1. 程式人生 > 實用技巧 >設計模式學習筆記(十六):職責鏈模式

設計模式學習筆記(十六):職責鏈模式

1 概述

1.1 引言

很多情況下,一個軟體系統中可以處理某個請求的物件不知一個,比如採購單的審批,主任,副董事長,董事長,董事會都可以處理採購單,他們可以構成一條處理採購單的鏈式結構,採購單沿著這條鏈進行傳遞,這條鏈就叫職責鏈。

職責鏈可以是一條直線,一個環或者一個樹形結構,最常見的職責鏈是直線型,即沿著一條單向的鏈來傳遞請求。鏈上的每一個物件都是請求處理者,職責鏈模式可以將請求的處理者組織成一條鏈,並讓請求沿著鏈傳遞,由鏈上的處理者對請求進行處理,客戶端無須關係請求的處理細節以及具體的傳遞,只需要將請求傳送到鏈上即可,實現請求傳送者以及請求處理者的解耦。

1.2 定義

職責鏈模式:避免將請求傳送者與接收者耦合在一起,讓多個物件都有機會接收請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,直到有物件處理它為止。

職責鏈模式是一種行為型模式。

1.3 結構圖

1.4 角色

  • Handler(抽象處理者):定義一個處理請求的介面,一般為抽象類。因為每一個處理者的下家還是一個處理者,因此在抽象處理者中定義了一個抽象處理者的物件作為對下一個處理者的引用,通過該引用,處理者可以連成一條鏈
  • ConcreteHandler(具體處理者):抽象處理者的子類,實現具體處理方法,在處理前需要判斷是否具有處理許可權,如果擁有許可權則處理,沒有則轉發到下一個處理者

2 典型實現

2.1 步驟

  • 定義抽象處理者:定義處理請求介面以及定義一個抽象處理者成員,作為下一個處理者的引用,一般為了讓具體處理者方便呼叫,定義為protected
  • 定義具體處理者:處理/轉發請求,處理請求前先判斷是否具有許可權 ,擁有則處理請求,否則轉發請求
  • 客戶端建立職責鏈:職責鏈模式並不建立職責鏈,職責鏈交由客戶端建立,根據實際需要定義職責鏈順序

2.2 抽象處理者

abstract class Handler
{
    protected Handler successor;
    public void setSuccessor(Handler successor)
    {
        this.successor = successor;
    }

    public abstract void handleRequest(int num);
}

擁有一個設定下一處理者的物件,可以通過setter注入,同時宣告抽象處理方法。

2.3 具體處理者

class ConcreteHandler1 extends Handler
{
    @Override    
    public void handleRequest(int num)
    {
        if(num < 10)
        {
            System.out.println("處理小於10的數字:"+num);
        }
        else
            successor.handleRequest(num);
    }
}

class ConcreteHandler2 extends Handler
{
    @Override    
    public void handleRequest(int num)
    {
        if(num < 20)
        {
            System.out.println("處理大於等於10且小於20的數字:"+num);
        }
        else
            successor.handleRequest(num);
    }
}

class ConcreteHandler3 extends Handler
{
    @Override    
    public void handleRequest(int num)
    {
        if(num < 30)
        {
            System.out.println("處理大於等於20且小於30的數字:"+num);
        }
        else
            successor.handleRequest(num);
    }
}

繼承抽象處理者,首先判斷是否擁有許可權處理(這裡是一個簡單的if判斷),如果有就處理,沒有的話通過protected物件,也就是轉發給下一個處理者處理。

2.4 客戶端

public static void main(String[] args) 
{
    Handler handler = new ConcreteHandler1();
    Handler handler2 = new ConcreteHandler2();
    Handler handler3 = new ConcreteHandler3();
    handler.setSuccessor(handler2);
    handler2.setSuccessor(handler3);

    handler.handleRequest(3);
    handler.handleRequest(15);
    handler.handleRequest(22);
}

客戶端針對抽象處理者程式設計,需要建立每一個具體處理者物件,並且自定義職責鏈:

handler.setSuccessor(handler2);
handler2.setSuccessor(handler3);

接著呼叫對應的處理者處理即可。

3 例項

設計一個採購單審批系統,分級進行,根據金額不同由不同層次的人員審批,主任可以審批5w以下的採購單,副董事長可以審批5w-10w,董事長可以審批10w-50w,50w以上需要由董事會審批,使用職責鏈模式設計該系統。

設計如下:

  • 抽象處理者:Approver
  • 具體處理者:Director+VicePresident+President+Congress
  • 採購單請求類:PurchaseRequest

程式碼如下:

//抽象處理者
abstract class Approver
{
    protected Approver successor;

    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    public abstract void processRequest(PurchaseRequest request);
}

//具體處理者:主任
class Director extends Approver
{
    @Override
    public void processRequest(PurchaseRequest request)
    {
        if(request.getAmount() < 50000)
            System.out.println("主任審批一筆\n金額為"+request.getAmount()+"\nid為"+request.getId()+"\n的採購單\n");
        else
            successor.processRequest(request);
    }
}

//具體處理者:副董事長
class VicePresident extends Approver
{
    @Override
    public void processRequest(PurchaseRequest request)
    {
        if(request.getAmount() < 100000)
            System.out.println("副董事長審批一筆\n金額為"+request.getAmount()+"\nid為"+request.getId()+"\n的採購單\n");
        else
            successor.processRequest(request);
    }
}

//具體處理者:董事長
class President extends Approver
{
    @Override
    public void processRequest(PurchaseRequest request)
    {
        if(request.getAmount() < 500000)
            System.out.println("董事長審批一筆\n金額為"+request.getAmount()+"\nid為"+request.getId()+"\n的採購單\n");
        else
            successor.processRequest(request);
    }
}

//具體處理者:董事會
class Congress extends Approver
{
    @Override
    public void processRequest(PurchaseRequest request)
    {
        System.out.println("董事會審批一筆\n金額為"+request.getAmount()+"\nid為"+request.getId()+"\n的採購單\n");
    }
}

//請求類:採購單
class PurchaseRequest
{
    private double amount;
    private String id;
    private static final String STR = "xcnvj232cvm";
    private static final Random random = new Random();

    public PurchaseRequest(double amount)
    {
        this.amount = amount;
        //簡易的隨機字串
        this.id = STR.substring(0,random.nextInt(STR.length()-1)+1).repeat(random.nextInt(3)+2);
    }

    public double getAmount() {
        return this.amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public String getId() {
        return this.id;
    }

    public void setId(String id) {
        this.id = id;
    }

}

測試:

public static void main(String[] args) 
{
    Approver director = new Director();
    Approver vicePresident = new VicePresident();
    Approver president = new President();
    Approver congress = new Congress();

    director.setSuccessor(vicePresident);
    vicePresident.setSuccessor(president);
    president.setSuccessor(congress);

    director.processRequest(new PurchaseRequest(12345));
    director.processRequest(new PurchaseRequest(54321));
    director.processRequest(new PurchaseRequest(123456));
    director.processRequest(new PurchaseRequest(654321));
}

輸出如下:

4 分類

職責鏈模式可以分為純的職責鏈模式與不純的職責鏈模式。

4.1 純的職責鏈模式

一個純的職責鏈模式要求一個具體處理者物件只能在兩個行為中選擇一個,要麼承擔全部責任,要麼將責任推給下家,不允許出現某一個具體處理者物件在承擔了一部分或全部責任後又將責任向下傳遞的情況。

而且在純的職責鏈模式中,要求一個請求必須被某一個處理者物件接收,不能出現某個請求未被任何一個處理者物件處理的情況,比如前面的採購單例子。

4.2 不純的職責鏈模式

在一個不純的職責鏈模式中,允許某個請求被一個具體處理者部分處理後再向下傳遞,或者一個具體處理者處理完某請求後其猴戲處理者可以繼續處理該請求,而且一個請求可以最終不被任何處理者物件所接收。

在Java AWT 1.0中的事件處理模型應用就是不純的職責鏈模式,基本原理如下:由於視窗元件一般位於容器元件中,當事件發生在視窗元件上時,先通過元件物件的handleEvent()方法傳遞給相應的事件處理方法,該事件處理方法將處理該事件,然後決定是否將該事件向上一級容器元件傳播,上級容器元件在接到事件之後可以繼續處理此事件並決定是否繼續向上級容器元件傳播,直到頂層容器元件為止。如果一直都沒有處理方法則不處理該事件。

這種事件處理機制又叫事件浮升機制,JDK1.1後使用觀察者模式來代理職責鏈模式處理事件。

5 主要優點

  • 降低耦合:職責鏈模式使得一個物件無須知道是其他哪一個物件處理請求,物件僅需知道請求會被處理即可,接收者和傳送者都沒有對方明確資訊,且鏈中物件不需要知道鏈的結構,由客戶端負責鏈的建立,降低了系統耦合度
  • 簡化物件連線:請求處理物件僅需維持一個指向其後繼者的引用,而不需要維持它對所有候選處理者的引用,可簡化物件的相互連線
  • 靈活的職責鏈:可以在執行時對鏈進行動態增加或者修改
  • 符合OCP:系統增加一個新的具體處理者時無須修改原始碼,只需要客戶端重建職責鏈,符合OCP

6 主要缺點

  • 請求可能得不到處理:由於一個請求沒有明確的接收者,因此請求不一定會被處理,也有可能因為職責鏈配置錯誤而得不到處理
  • 效能受到影響:對於較長的職責鏈,請求的處理可能涉及多個處理物件,系統性能會受到一定影響,而且程式碼除錯時可能不方便
  • 死迴圈:如果職責鏈不當,可能會導致死迴圈呼叫

7 適用場景

  • 有多個物件可以處理同一個請求,具體哪個物件處理該請求待執行時刻再確定,客戶端只需將請求提交到鏈上,而無須關心請求的處理物件是誰以及它是如何處理的
  • 在不明確指定接收者的情況下,向多個物件的一個提交一個請求
  • 可動態指定一組物件處理請求,客戶端可以動態建立職責鏈來處理請求,還可以改變鏈中處理請求以及處理者之間的先後次序

8 總結

如果覺得文章好看,歡迎點贊。

同時歡迎關注微信公眾號:氷泠之路。