1. 程式人生 > >設計模式之職責鏈

設計模式之職責鏈

num alt des basis 對象處理 關系 osi 簡單的 頭上

職責鏈(Chain of Responsibility)模式屬於23種設計模式之一,職責鏈也稱為責任鏈,《Design pattern: the basis of reusable object-oriented software》(以下簡稱DP)一書中是這樣描述職責鏈的:職責鏈模式使多個對象都有機會處理請求,從而避免請求發送者和接收者之間的耦合關系。將這個對象連成一條鏈,並沿這條鏈傳遞該請求,直到有一個對象處理它為止。

DP中對職責鏈模式的定義稍微不是那麽的好理解,簡單來說就是將能夠處理用戶請求的對象都串成一條鏈,然後將用戶的請求放進這條鏈裏,這個請求就可以在鏈中的對象之間傳遞,一直傳遞到能夠處理它的對象上為止。

可能這麽說還不太好理解,我們可以舉個生活中的例子,例如員工申請加薪。我們都知道普通員工想要申請加薪,首先需要將加薪的申請書提交給主管,如果主管沒有權限處理這個加薪的事情,那麽主管就會將申請提交到部門經理那邊,由部門經理去處理。如果部門經理也沒有權處理的話,就會將申請再提交到總經理或人力資源總監手上,總經理說我可以處理,那麽這個加薪申請就可以被處理了。而普通員工並不知道他提交的這個加薪申請被誰處理了,也不知道處理的經過,他只需要知道將申請提交給主管,最後等結果即可。這就是一個典型的職責鏈機制:客戶端發送請求到職責鏈的鏈頭上,然後這個請求就會在鏈中的對象之間逐個傳遞,直到被某個對象處理並返回結果給客戶端為止。

示意圖:
技術分享圖片

不使用職責連模式設計的代碼:

就拿以上所說到的例子,我們來用代碼做一個簡單的試驗,先不使用職責鏈模式,就用最簡單方式去實現這個場景。

1.編寫一個RaisesRequest類,用來封裝請求內容:

package org.zero01.test;

public class RaisesRequest {

    public String getRequestName() {
        return requestName;
    }

    public void setRequestName(String requestName) {
        this.requestName = requestName;
    }

    public int getRequestNumber() {
        return requestNumber;
    }

    public void setRequestNumber(int requestNumber) {
        this.requestNumber = requestNumber;
    }

    private String requestName;
    private int requestNumber;

}

2.編寫管理者類Manager,這個類用於處理用戶的請求:

package org.zero01.test;

import org.zero01.test.RaisesRequest;

public class Manager {

    // 返回處理結果
    public void getResult(String position, RaisesRequest raisesRequest) {

        if (position.equals("主管")) {

            if (raisesRequest.getRequestName().equals("加薪")) {
                System.out.println("主管:我無權處理");
            } else {
                System.out.println("主管:你在說啥");
            }

        } else if (position.equals("部門經理")) {

            if (raisesRequest.getRequestName().equals("加薪")) {
                System.out.println("部門經理:我無權處理");
            } else {
                System.out.println("部門經理:你在說啥");
            }

        } else if (position.equals("人力資源總監")) {

            if (raisesRequest.getRequestName().equals("加薪")) {
                System.out.println("人力資源總監:我無權處理");
            } else {
                System.out.println("人力資源總監:你在說啥");
            }

        } else if (position.equals("總經理")) {

            if (raisesRequest.getRequestName().equals("加薪") && raisesRequest.getRequestNumber() <= 1000) {
                System.out.println("總經理:ok,批準了");
            } else if(raisesRequest.getRequestName().equals("加薪") && raisesRequest.getRequestNumber() > 1000){
                System.out.println("總經理:emmm我考慮一下");
            }else{
                System.out.println("總經理:你在說啥");
            }

        } else {
            System.out.println("沒有這個職位");
        }
    }
}

3.最後編寫客戶端類Client:

package org.zero01.test;

public class Client {

    public static void main(String[] args) {

        // 封裝請求內容
        RaisesRequest raisesRequest = new RaisesRequest();
        raisesRequest.setRequestName("加薪");
        raisesRequest.setRequestNumber(1000);

        // 發送給不同的管理者
        Manager manager = new Manager();
        manager.getResult("主管", raisesRequest);
        manager.getResult("部門經理", raisesRequest);
        manager.getResult("人力資源總監", raisesRequest);
        manager.getResult("總經理", raisesRequest);

    }
}

運行結果:

主管:我無權處理
部門經理:我無權處理
人力資源總監:我無權處理
總經理:ok,批準了

ok,從運行結果也可以看到,這樣的代碼也是勉勉強強實現了我們的需求。但是,很直觀的可以看到代碼是有多糟糕,Manager類裏的if else全都集中在一個方法裏,造成一個方法裏重復的代碼太多。而且不同部門的管理者都在這個方法裏,這就違反了單一職責原則,如果我要增加一個部門的管理者或減少一個部門的管理者,都需要去修改這個類裏的代碼以及客戶端的代碼,違反了開-閉原則。這樣的代碼耦合性是相當高的,但是我們要怎麽樣對這些代碼進行解耦呢?那麽這時候就需要使用到職責鏈模式了。

職責鏈模式結構圖:
技術分享圖片

職責連模式示例代碼:
1.Handler類,定義一個處理請求的接口:

package org.zero01.test;

public abstract class Handler {

    protected Handler successor;

    // 設置繼任者
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract void handlerRequest(int request);

}

2.ConcreteHandler1類,是一個具體的處理類,它只能處理它所負責的請求,不能處理的就傳遞給它的後繼者處理:

package org.zero01.test;

public class ConcreteHandler1 extends Handler {

    public void handlerRequest(int request) {
        // 處理0-10之間的請求
        if (request >= 0 && request <= 10) {
            System.out.println("ConcreteHandler1處理請求" + request);
        } else if (successor != null) {
            // 將請求傳遞到下一位
            successor.handlerRequest(request);
        }
    }
}

3.ConcreteHandler2:

package org.zero01.test;

public class ConcreteHandler2 extends Handler {

    public void handlerRequest(int request) {
        // 處理10-20之間的請求
        if (request >= 10 && request <= 20) {
            System.out.println("ConcreteHandler2處理請求" + request);
        } else if (successor != null) {
            // 將請求傳遞到下一位
            successor.handlerRequest(request);
        }
    }
}

4.ConcreteHandler3:

package org.zero01.test;

public class ConcreteHandler3 extends Handler {

    public void handlerRequest(int request) {
        // 處理20-30之間的請求
        if (request >= 20 && request <= 30) {
            System.out.println("ConcreteHandler3處理請求" + request);
        } else if (successor != null) {
            // 將請求傳遞到下一位
            successor.handlerRequest(request);
        }
    }
}

5.Client,客戶端類,在該類中向職責鏈提交請求:

package org.zero01.test;

public class Client {

    public static void main(String[] args) {

        Handler h1 = new ConcreteHandler1();
        Handler h2 = new ConcreteHandler2();
        Handler h3 = new ConcreteHandler3();

        // 設置職責鏈的上家與下家,h1作為鏈頭
        h1.setSuccessor(h2);
        h2.setSuccessor(h3);

        int[] numbers = {10, 20, 30, 51, 25, 5, 8, 7, 15, 47};

        for (int i : numbers) {
            h1.handlerRequest(i);
        }
    }
}

運行結果:

ConcreteHandler1處理請求10
ConcreteHandler2處理請求20
ConcreteHandler3處理請求30
ConcreteHandler3處理請求25
ConcreteHandler1處理請求5
ConcreteHandler1處理請求8
ConcreteHandler1處理請求7
ConcreteHandler2處理請求15

職責鏈的好處:

從以上這個小示例可以看到,每當我從客戶端提交一個請求時,請求是沿著職責鏈傳遞的,直至傳遞到有對應的ConcreteHandler對象來處理它。請求的處理著與發送者都不需要知道對方的明確信息,且鏈中的對象自己也並不知道鏈的結構。所以職責鏈可以簡化對象之間的互相連接,它們僅需要保持一個指向其後繼者的引用,而不需要保持它所有的候選者的引用,從這點也可以看出職責鏈是單鏈的結構,這樣大大的降低了類之間的耦合度,因為它們都只依賴於一個抽象父類。

類之間低耦合的好處就是可以隨時增加、減少以及修改某個處理請求的結構代碼,增強了給對象指派職責的靈活性。但是要註意一個請求可能到了鏈的末端都得不到處理,或者因為沒有正確配置而得不到處理。這時候估計就有人註意到以上代碼的一個小細節,數值大於30的請求都沒有被處理,這就是典型的請求被傳遞到了鏈的末端也得不到處理。所以在設計代碼時還需要仔細考慮全面,如果是上面這個例子我們就可以簡單的加一個else分支來處理這個問題,而不同的場景下需要根據實際情況使用相應的方式去處理。

使用職責連模式重構之前的代碼:

我們順便把需求更改一下,讓不同的管理者可以處理不同額度的加薪請求。

代碼結構圖:
技術分享圖片

1.抽象一個Manager管理類:

package org.zero01.test;

public abstract class Manager {

    protected Manager superior;
    protected String position;

    // 設置管理對象的名稱
    public Manager(String position) {
        this.position = position;
    }

    // 設置上級對象
    public void setSuperior(Manager superior) {
        this.superior = superior;
    }

    // 處理加薪請求的方法
    public abstract void raisesRequest(String requestName,int requestNumber);

}

2.編寫主管類,這是具體的處理類,可以處理100元以內的加薪請求:

package org.zero01.test;

public class Director extends Manager {

    public Director(String position) {
        super(position);
    }

    public void raisesRequest(String requestName, int requestNumber) {

        // 有權處理100元以內的加薪
        if (requestName.equals("加薪") && requestNumber <= 100) {
            System.out.println(position + ":ok,批準了");
        } else if (superior != null) {
            // 將請求傳遞給上級
            superior.raisesRequest(requestName, requestNumber);
        }

    }
}

3.部門經理類,可以處理300元以內的加薪請求:

package org.zero01.test;

public class BranchManager extends Manager{

    public BranchManager(String position) {
        super(position);
    }

    public void raisesRequest(String requestName, int requestNumber) {

        // 有權處理300元以內的加薪
        if (requestName.equals("加薪") && requestNumber <= 300) {
            System.out.println(position + ":ok,批準了");
        } else if (superior != null) {

            // 將請求傳遞給上級
            superior.raisesRequest(requestName, requestNumber);
        }
    }
}

4.人力資源總監類,可以處理600元以內的加薪請求:

package org.zero01.test;

public class CHO extends Manager{

    public CHO(String position) {
        super(position);
    }

    public void raisesRequest(String requestName, int requestNumber) {

        // 有權處理600元以內的加薪
        if (requestName.equals("加薪") && requestNumber <= 600) {
            System.out.println(position + ":ok,批準了");
        } else if (superior != null) {

            // 將請求傳遞給上級
            superior.raisesRequest(requestName, requestNumber);
        }
    }
}

5.總經理類,可以處理1000元以上的加薪請求:

package org.zero01.test;

public class GeneralManager extends Manager{

    public GeneralManager(String position) {
        super(position);
    }

    public void raisesRequest(String requestName, int requestNumber) {

        // 有權處理1000元以上的加薪
        if (requestName.equals("加薪") && requestNumber <= 1000) {
            System.out.println(position + ":ok,批準了");
        }else {
            // 1000元以上看心情
            System.out.println(position + ":emmm我考慮一下");
        }
    }
}

6.員工類,也就是客戶端類:

package org.zero01.test;

public class Staff {

    public static void main(String[] args) {

        // 實例化各個管理者對象
        Manager director = new Director("主管");
        Manager branchManager = new BranchManager("部門經理");
        Manager cho = new CHO("人力資源總監");
        Manager generalManager = new GeneralManager("總經理");

        // 設置上級與下級
        director.setSuperior(branchManager);
        branchManager.setSuperior(cho);
        cho.setSuperior(generalManager);

        director.raisesRequest("加薪",100);
        director.raisesRequest("加薪",300);
        director.raisesRequest("加薪",600);
        director.raisesRequest("加薪",1000);
        director.raisesRequest("加薪",1500);

    }
}

運行結果:

主管:ok,批準了
部門經理:ok,批準了
人力資源總監:ok,批準了
總經理:ok,批準了
總經理:emmm我考慮一下

使用職責鏈模式來重構之前的代碼後,就很好的解決了原來大量的分支判斷耦合在一起,造成維護困難、靈活性差且不好擴展的問題。

設計模式之職責鏈