「補課」進行時:設計模式(9)——在請假中使用的責任鏈模式

1. 前文匯總

「補課」進行時:設計模式系列

2. 請假

作為一位新時代的社畜,我們每天起得比雞早,睡得比狗晚,還時不時的要受到上司的 PUA ,每天都生活在水深火熱之中。

生活中總會有各種意外,比如生病了,需要去醫院看病,那我們需要請假去醫院,一般在公司中,請假的時長和審批領導息息相關,如果這個規則是這樣的:

  • 請假 3 天內小組長可以審批
  • 請假 5 天內需要大組長神品
  • 請假 20 天內需要部門經理審批

如果按照順序思維來寫程序的話,那麼我們需要做大量的 if…else 的判斷,並且所有的類都要耦合在一起,這時,我們可以使用責任鏈模式,上面的審批流成如下:

我們可以先定義一個員工類:

public interface IPerson {
    // 獲取當前請假天數
    int getDays();
    // 獲取審批結果
    String getResult();
}

public class Person implements IPerson {

    private int days;
    private String message;

    public Person(int days) {
        this.days = days;
        this.message = "領導,我想請 " + days + " 天假!!!";
    }

    @Override
    public int getDays() {
        return this.days;
    }

    @Override
    public String getResult() {
        return this.message;
    }
}

創建一個抽象 Handler 類:

public abstract class Handler {

    public final static int MIN = 3;
    public final static int MIDDLE = 5;
    public final static int MAX = 20;

    // 當前能處理的級別
    private int days;
    // 責任傳遞,定義下一個責任人
    private Handler nextHandler;
    // 所有的類都需要定義自己的能處理的請假天數
    public Handler(int days) {
        this.days = days;
    }

    public final void handleMessage(IPerson person) {
        if (this.days > person.getDays()) {
            this.response(person);
        } else {
            if (this.nextHandler != null) {
                this.nextHandler.handleMessage(person);
            } else {
                System.out.println("員工想要請假 " + this.days + " 天,超過可以審批的最大權限,那就不批了");
            }
        }
    }

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    protected abstract void response(IPerson person);
}

這個類中最核心的部分是定義了 nextHandler 下一個責任人,如果當前的員工請假的請求不屬於自己的審批範疇,則會將這個請求轉發至下一個審批人。

接下來是三位具體的審批人:

public class Leader1 extends Handler {
    // 小組長
    public Leader1() {
        super(Handler.MIN);
    }

    @Override
    protected void response(IPerson person) {
        System.out.println("-----------向小組長請示------------");
        System.out.println(person.getResult());
        System.out.println("-----------請示通過---------------");
    }
}

public class Leader2 extends Handler {
    // 大組長
    public Leader2() {
        super(Handler.MIDDLE);
    }

    @Override
    protected void response(IPerson person) {
        System.out.println("-----------向大組長請示------------");
        System.out.println(person.getResult());
        System.out.println("-----------請示通過---------------");
    }
}

public class Leader3 extends Handler {
    // 部門經理
    public Leader3() {
        super(Handler.MAX);
    }

    @Override
    protected void response(IPerson person) {
        System.out.println("-----------向部門經理請示--------------");
        System.out.println(person.getResult());
        System.out.println("-----------請示通過---------------");
    }
}

然後我們來寫一個測試類:

public class Test {
    public static void main(String[] args) {

        Random random = new Random();

        ArrayList<IPerson> personList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            personList.add(new Person(random.nextInt(30)));
        }

        Handler leader1 = new Leader1();
        Handler leader2 = new Leader2();
        Handler leader3 = new Leader3();

        leader1.setNextHandler(leader2);
        leader2.setNextHandler(leader3);

        for (IPerson person: personList) {
            leader1.handleMessage(person);
        }
    }
}

執行結果如下:

-----------向部門經理請示--------------
領導,我想請 17 天假!!!
-----------請示通過---------------
員工想要請假 20 天,超過可以審批的最大權限,那就不批了
-----------向小組長請示------------
領導,我想請 0 天假!!!
-----------請示通過---------------
-----------向部門經理請示--------------
領導,我想請 11 天假!!!
-----------請示通過---------------
-----------向大組長請示------------
領導,我想請 4 天假!!!
-----------請示通過---------------

3. 責任鏈模式

責任鏈模式定義如下:

Avoid coupling the sender of a request to its receiver by giving more thanone object a chance to handle the request.Chain the receiving objects andpass the request along the chain until an object handles it.(使多個對象都有機會處理請求,從而避免了請求的發送者和接受者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它為止。)

責任鏈模式的重點是在 「鏈」 上,由一條鏈去處理相似的請求在鏈中決定誰來處理這個請求,並返回相應的結果,這個鏈類似於一個單向鏈表的數據結構,從開始一直向後迭代,知道找不到下一個為止,它的通用類圖如下:

通用代碼如下:

3.1 抽象 Handler

public abstract class Handler {
    private Handler nextHandler;

    // 每個處理者都必須對請求作出處理
    public final Response handleMessage(Request request) {
        Response response = null;
        // 判斷當前處理級別
        if (this.getHandlerLevel().equals(request.getLevel())) {
            response = this.echo(request);
        } else {
            // 判斷是否有下一個處理者
            if (this.nextHandler != null) {
                response = this.nextHandler.handleMessage(request);
            } else {
                // 沒有匹配的業務處理者,邏輯根據具體場景實現
            }
        }
        return response;
    }
    // 設置下一個處理者
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    protected abstract Level getHandlerLevel();

    protected abstract Response echo(Request request);
}

抽象的處理者實現三個職責:

  • 定義一個請求的處理方法 handleMessage ,唯一對外開放的方法。
  • 定義一個鏈的編排方法 setNext ,設置下一個處理者。
  • 定義了具體的請求者必須實現的兩個方法:定義自己能夠處理的級別 getHandlerLevel 和具體的處理任務 echo 。

3.2 具體處理者

public class ConcreteHandler1 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        // 設置自己的處理級別
        return null;
    }

    @Override
    protected Response echo(Request request) {
        // 設置處理的業務功能
        return null;
    }
}

public class ConcreteHandler2 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        return null;
    }

    @Override
    protected Response echo(Request request) {
        return null;
    }
}

public class ConcreteHandler3 extends Handler {
    @Override
    protected Level getHandlerLevel() {
        return null;
    }

    @Override
    protected Response echo(Request request) {
        return null;
    }
}

定義三個具體的處理者,以便組成一個鏈。

3.3 其餘相關代碼

public class Level {
}

public class Request {
    public Level getLevel() {
        return null;
    }
}

public class Response {
}

3.4 測試類

最後是一個測試類:

public class Test {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        Handler handler3 = new ConcreteHandler3();

        handler1.setNextHandler(handler2);
        handler2.setNextHandler(handler3);

        Response response = handler1.handleMessage(new Request());
    }
}

在實際的使用過程中,最後都會有一個封裝類對責任鏈模式進行封裝,用來取代我們現在的這個測試類,直接返回鏈中的第一個處理者,具體鏈的設置不需要高層次模塊關係,這樣,更簡化了高層次模塊的調用,減少模塊間的耦合,提高系統的靈活性。

4. 優點

任鏈模式非常顯著的優點是將請求和處理分開。請求者可以不用知道是誰處理的,處理者可以不用知道請求的全貌(例如在 J2EE 項目開發中,可以剝離出無狀態 Bean 由責任鏈處理),兩者解耦,提高系統的靈活性。

5. 缺點

責任鏈有兩個非常顯著的缺點:

  • 性能問題,每個請求都是從鏈頭遍歷到鏈尾,特別是在鏈比較長的時候,性能是一個非常大的問題。
  • 調試不很方便,特別是鏈條比較長,環節比較多的時候,由於採用了類似遞歸的方式,調試的時候邏輯可能比較複雜。