天天看點

十、政策模式與責任鍊模式詳解

16.政策模式

16.1.課程目标

1、 掌握政策模式和責任鍊模式的應用場景;

2、 通過學習政策模式來消除程式中大量的if...else...和 switch語 句 ;

3、 掌握政策模式和委派模式的結合使用;

4、 深刻了解責任鍊模式和建造者模式的結合應用

16.2.内容定位

1 、已經掌握建造者模式和委質模式的人群。

2、希望通過對政策模式的學習,來消除程式中大量的備援代碼和多重條件轉移語句的人群。

3、希望通過學習責任鍊模式宜構校驗邏輯的人群。

16.3.定義

政策模式 (Strategy Pattern) 又叫也叫政策擺式 (Policy Pattern) , 它是将定義的算法家族、分

别封裝起來,讓它們之間可以互相替換,進而讓算法的變化不會影響到使用算法的使用者。屬千行為型模式。

原 文: Define a family of algorithms,encapsulate each one,and make them interchangeable.

政策擺式使用的就是面向對象的繼承和多态機制,進而實作同—行為在不同場萦下具備不同實作。

16.4.應用場景

政策模式在生活場景中應用也非常多。比如—個入的交稅比率與他的工資有關,不同的工資水準對

應不同的稅率。再比如我們在網際網路移動支付的大背景下,每次下單後付款前,需要選擇支付方式。

政策模式可以解決在有多種算法相似的情況下,使用 if…else 或 switch... case 所帶來的複雜性和臃腫性。在日常業務開發中,政策模式适用于以下場景 :

1 、針對同—類型問題,有多種處理方式,每—種都能獨立解決問題 ;

2、 算法需要自由切換的場果 ;

3、需要屏蔽算法規則的場果。

首先來看下政策模式的通用 UML 類圖 :

從 UML 類圖中,我們可以看到,政策擺式主要包含三種角色 :

上下文角色 (Context) : 用來操作政策的上下文環境,屏蔽高層子產品(用戶端)對政策, 算法的

直接通路,封裝可能存在的變化 ;

抽象政策角色 (Strategy) : 規定政策或算法的行為 ;

具體政策角色 (ConcreteStrategy) : 具體的政策或算法實作。

注意: 政策模式中的上下文環境 (Context) , 其職責本來是隔離用戶端與政策類的耦合, 讓用戶端完全與上下文環境 溝通, 無需關系具體政策。

16.5.促銷優惠業務場景

優惠政策會有很多種可能如 : 領取優惠券抵扣、返現促銷、拼團優惠。下面我們用代碼來模拟,

首先我們建立—個促銷政策的抽象

PromotionStrategy :

/**
 * 促銷政策抽象 
 */
public interface IPromotionStrategy {
    void doPromotion();
}           

然後分别建立優惠券抵扣政策 CouponStrategy 類、返現促銷政策 CashbackStrategy 類、拼團優惠政策 GroupbuyStrategy 類和無優惠政策 EmptyStrategy 類 :

public class CouponStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("使用優惠券抵扣");
    }
}           

CashbackStrategy 類 :

public class CashbackStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("返現,直接打款到支付寶賬号");
    }
}           

GroupbuyStrategy 類 :

public class GroupbuyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("5人成團,可以優惠");
    }
}           

EmptyStrategy 類 :

public class EmptyStrategy implements IPromotionStrategy {
    public void doPromotion() {
        System.out.println("無優惠");
    }
}           

然後建立促銷活動方案 PromotionActivity 類 :

public class PromotionActivity {
    private IPromotionStrategy strategy;

    public PromotionActivity(IPromotionStrategy strategy) {
        this.strategy = strategy;
    }

    public void execute(){
        strategy.doPromotion();
    }
}           

編寫用戶端測試類 :

public class Test {
    public static void main(String[] args) {
        PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
        PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
        activity618.execute();
        activityllll.execute();
    }
}           

運作效果如下:

使用優惠券抵扣
返現,直接打款到支付寶賬号           

此時,小夥伴們會發現,如果把上面這段則試代碼放到實際的業務場景其實并不實用。因為我們做活動時候往往是要根據不同的需求對促銷政策進行動态選擇的,并不會一次性執行多種優惠。是以,我

們的代碼通常會這樣寫 :

public class Test {
    public static void main(String[] args) {
        PromotionActivity promotionActivity = null;
        String promotionKey = "COUPON";
        if (StringUtils.equals(promotionKey, "COUPON")) {
            promotionActivity = new PromotionActivity(new CouponStrategy());
        } else if (StringUtils.equals(promotionKey, "CASHBACK")) {
            promotionActivity = new PromotionActivity(new CashbackStrategy());
        }
        promotionActivity.execute();
    }
}           

這樣改造之後,滿足了業務需求,客戶可根據自己的需求選擇不同的優惠政策了。但是,經過—段

時間的業務積累,我們的促銷活動會越來越多。千是,我們的程式猿小哥哥就忙不來了,每次上活動之前都要通宵改代碼,而且要做重複測試,判斷邏輯可能也變得越來越複雜。這時候,我們是不需要思考

代碼是不是應該重構了?回顧我們之前學過的設計模式應該如何來優化這段代碼呢?其實,我們可以結

合單例模式和工廠擺式。建立 PromotionStrategyFactory 類 :

public class PromotionStrategyFacory {

    private static final IPromotionStrategy EMPTY = new EmptyStrategy();
    private static Map<String, IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();

    static {
        PROMOTIONS.put(PromotionKey.COUPON, new CouponStrategy());
        PROMOTIONS.put(PromotionKey.CASHBACK, new CashbackStrategy());
        PROMOTIONS.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
    }

    private PromotionStrategyFacory() {
    }

    public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
        IPromotionStrategy strategy = PROMOTIONS.get(promotionKey);
        return strategy == null ? EMPTY : strategy;
    }

    public static Set<String> getPromotionKeys() {
        return PROMOTIONS.keySet();
    }

    private interface PromotionKey {
        String COUPON = "COUPON";
        String CASHBACK = "CASHBACK";
        String GROUPBUY = "GROUPBUY";
    }
}           

這時候我們用戶端代碼就應該這樣寫了 :

public class Test {
    public static void main(String[] args) {
        PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
        PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
        activity618.execute();
        activityllll.execute();
    }
}           
使用優惠券抵扣           

代碼優化之後 , 是不是我們程式猿小哥哥的維護工作就輕松了?每次上新活動,不影響原來的代碼

邏輯。

16.6.用政策模式實作選擇支付方式的業務場景

為了加深對政策模式的了解,我們再來舉一個案例。 相信小夥伴們都用過支付寶、微信支付、銀聯

支付以及京東白條。 —個常見的應用場景就是大家在下單支付時會提示選擇支付方式,如果使用者未選,

系統也會預設好推薦的支付方式進行結算。來看一下類圖,下面我們用政策模式來描拟此業務場罷 :

建立 Payment 抽象類,定義支付規範和支付邏輯,代碼如下 :

public abstract class Payment {

    public abstract String getName();

    //通用邏輯放到抽象類裡面實作
    public MsgResult pay(String uid, double amount) {
        //餘額是否足夠
        if (queryBalance(uid) < amount) {
            return new MsgResult(500, "支付失敗", "餘額不足");
        }
        return new MsgResult(200, "支付成功", "支付金額" + amount);
    }

    protected abstract double queryBalance(String uid);
}           

分别建立具體的支付方式,支付寶 AliPay類 :

public class AliPay extends Payment {
    public String getName() {
        return "支付寶";
    }

    protected double queryBalance(String uid) {
        return 900;
    }
}           

京東白條JDPay 類 :

public class JDPay extends Payment {
    public String getName() {
        return "京東白條";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}
           

微信支付 WechatPay類 :

public class WechatPay extends Payment {
    public String getName() {
        return "微信支付";
    }

    protected double queryBalance(String uid) {
        return 263;
    }
}           

銀聯支付 UnionPay 類 :

public class UnionPay extends Payment {
    public String getName() {
        return "銀聯支付";
    }

    protected double queryBalance(String uid) {
        return 120;
    }
}           

建立支付狀态的包裝類 MsgResult:

public class MsgResult {
    private int code;
    private Object data;
    private String msg;

    public MsgResult(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MsgResult{" +
                "code=" + code +
                ", data=" + data +
                ", msg='" + msg + '\'' +
                '}';
    }
}           

建立支付政策管理類 :

public class PayStrategy {
    public static  final String ALI_PAY = "AliPay";
    public static  final String JD_PAY = "JdPay";
    public static  final String WECHAT_PAY = "WechatPay";
    public static  final String UNION_PAY = "UnionPay";
    public static  final String DEFAULT_PAY = ALI_PAY;

    private static Map<String,Payment> strategy = new HashMap<String,Payment>();

    static {
        strategy.put(ALI_PAY,new AliPay());
        strategy.put(JD_PAY,new JDPay());
        strategy.put(WECHAT_PAY,new WechatPay());
        strategy.put(UNION_PAY,new UnionPay());
    }

    public static Payment get(String payKey){
        if(!strategy.containsKey(payKey)){
            return strategy.get(DEFAULT_PAY);
        }
        return strategy.get(payKey);
    }
}           

建立訂單 Order 類 :

public class Order {
    private String uid;
    private String orderId;
    private double amount;

    public Order(String uid, String orderId, double amount) {
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }

    public MsgResult pay(){
        return pay(PayStrategy.DEFAULT_PAY);
    }

    public MsgResult pay(String payKey){
        Payment payment = PayStrategy.get(payKey);
        System.out.println("歡迎使用" + payment.getName());
        System.out.println("本次交易金額為" + amount + ",開始扣款");
        return payment.pay(uid,amount);
    }
}           

測試代碼 :

public class Test {
    public static void main(String[] args) {
        Order order = new Order("1","2020031401000323",324.5);
        System.out.println(order.pay(PayStrategy.UNION_PAY));
    }
}           

運作結果 :

歡迎使用銀聯支付
本次交易金額為324.5,開始扣款
MsgResult{code=500, data=餘額不足, msg='支付失敗'}           

希望通過大家耳熟能詳的業務場景來舉例,讓小夥伴們更深刻地了解政策模式。希望小夥伴們在面

試和工作展現出自己的優勢。

16.7.政策模式在架構源碼中的展現

首先來看JDK 中—個比較常用的比較器 Comparator 接口,我們看到的—個大家常用的 compare()

方法,就是一個政策抽象實作 :

public interface Comparator<T> {
    int compare(T o1, T o2);
}           

Comparator抽象下面有非常多的實作類,我們經常會把 Comparator作為參數傳入作為排序政策,

例如 Arrays 類的 parallelSort 方法等 :

public class Arrays {
    public static void parallelSort(byte[] a) {
        int n = a.length, p, g;
        if (n <= MIN_ARRAY_SORT_GRAN ||
            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
            DualPivotQuicksort.sort(a, 0, n - 1);
        else
            new ArraysParallelSortHelpers.FJByte.Sorter
            (null, a, new byte[n], 0, n, 0,
             ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
             MIN_ARRAY_SORT_GRAN : g).invoke();
    }
}           

還有 TreeMap 的構造方法:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
}           

這就是 Comparator 在JDK原碼中的應用。那我們來看政策模式在 Spring源碼中的應用,來看

Resource 類 :

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return true;
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();
}           

我們雖然沒有直接使用 Resource 類,但是我們經常使用它的子類,例如 :

/*
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see ClassPathResource
 * @see FileSystemResource
 * @see PathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */           

還有一個非常典型的場罷, Spring 的初始化也采用了政策模式,不同的類型的類采用不同的初始化

政策。首先有一個 lnstantiationStrategy 接口,我們來看一下湧碼 :

public interface InstantiationStrategy {
    Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) throws BeansException;

    Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, Constructor<?> ctor, @Nullable Object... args) throws BeansException;

    Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args) throws BeansException;
}           

頂層的政策抽象非常簡單,但是它下面有兩種政策 SimplelnstantiationStrategy 和

CgIibSubclassingInsta ntiationStrategy , 我們看—下類圖 :

打開類圖我們還發現 CgIibSubclassingInstantiationStrategy 政策類還繼承了

SimplelnstantiationStrategy 類,說明在實際應用中多種政策之間還可以繼承使用。小夥們可以作為

—個參考,在實際業務場萦中,可以根據需要來設計。

16.8.政策模式的優缺點

優點 :

1 、政策模式符合開閉原則。

2、避免使用多重條件轉移語句,如 if…else.. 語句、 switch 語句

3、使用政策模式可以提高算法的保密性和安全性。

缺點 :

1 、用戶端必須知道所有的政策,并且自行決定使用哪一個政策類。

2、代碼中會産生非常多政策類,增加維護難度。

17.責任鍊模式

責任鍊模式 (Chain of Responsibility Pattern) 是将鍊中每一個節點看作是一個對象,每個節點處理

的清求均不同,且内部自動維護—個下—節點對象。當—個清求從鍊式的首端發出時,會沿看鍊的路徑

依次傳遞給每—個節點對象,直至有對象處理這個清求為止。屬千行為型模式。

原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to hand I e the request. Chain the receiving objects and pass the request along the chain until an object handles it.

解釋:使多個對象都有機會處理請求,進而避免了請求的發送者和接收者之間的耦合關系。将這些對象連成一條鍊,

并沿着這條鍊傳遞該請求,直到有一個對象處理它為止。

17.1.責任鍊樓式的應用場景

在日常生活中責任鍊梩式還是比較常見的,我們平時工作處理一些事務,往往是各部門協同合作的

完成莘—個任務。而每個部門都有各自的職責,是以,很多時候事謂完成—半,便會轉交給下—個部門,

直到所有部門都過一漁之後事謂才能完成。還有我們平時俗話說的過五關、斬六将其實也是一種責任鍊。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-rbOpRRVv-1584275821165)(

https://miion.coding.net/p/miion/d/image-hosting/git/raw/master/image-20200315163552414.png)

]

責任鍊模式主要是解耦了請求與處理,客戶隻需将清求發送到鍊上即可,無需關心請求的具體内容

和處理細節,請求會自動進行傳遞直至有節點對象進行處理。

适用于以下應用場景 :

1 、多個對象可以處理同一清求,但具體由哪個對象處理則在運作時動态決定;

2、在不明确指定接收者的情況下,向多個對象中的—個送出—個請求;

3、可動态指定一組對象處理請求。

首先來看下責任鍊模式的通用 UML 類圖 :

從 UML類圖中,我們可以看到,責任鍊模式主要包含兩種角色:

抽象處理者(Handler):定義一個請求處理的方法,并維護一個下一個處理節點Handler對象的

引用;

具體處理者(ConcreteHandler):對請求進行處理,如果不感興趣,則進行轉發。

責任鍊模式的本質是解耦請求與處理,讓請求在處理鍊中能進行傳遞與被處理;了解責任鍊模式應

當了解的是其模式(道 )而不是其具體實作(術 ),責任鍊模式的獨到之處是其将節點處理者組合成了

鍊式結構,并允許節點自身決定是否進行請求處理或轉發,相當于讓請求流動了起來。

17.2.利用責任鍊模式進行資料校驗攔截

首 先

, 建立一個實體類Member :

@Data
public class Member {
    private String loginName;
    private String loginPass;
    private String roleName;

    public Member(String loginName, String loginPass) {
        this.loginName = loginName;
        this.loginPass = loginPass;
    }
}           

然後來看一段我們經常寫的代碼:

public class MemberService {
    public static void main(String[] args) {
        MemberService service = new MemberService();
        service.login("tom", "666");
    }

    public void login(String loginName, String loginPass) {
        if (StringUtils.isEmpty(loginName) ||
                StringUtils.isEmpty(loginPass)) {
            System.out.println("使用者名和密碼為空");
            return;
        }
        System.out.println("使用者名和密碼不為空,可以往下執行");
        Member member = checkExists(loginName, loginPass);
        if (null == member) {
            System.out.println("使用者不存在");
            return;
        }
        System.out.println("登入成功!");
        if (!"管理者".equals(member.getRoleName())) {
            System.out.println("您不是管理者,沒有操作權限");
            return;
        }
        System.out.println("允許操作");
    }

    private Member checkExists(String loginName, String loginPass) {
        Member member = new Member(loginName, loginPass);
        member.setRoleName("管理者");
        return member;
    }
}           

請問各位小夥伴,你們是不是經常這麼幹?上面的代碼中,主要功能是做了登入前的資料驗證,然

, 判斷邏輯是有先後順序的。首先做非空判斷,然後檢查賬号是否有效,最終獲得使用者角色。然後根

據使用者角色所擁有的權限再比對是否有操作權限。那麼這樣的檢驗性代碼一般都是必不可少的

, 但是寫在具體的業務代碼又顯得代碼非常臃腫,是以我們可以用責任鍊模式

, 将這些檢查步驟串聯起來,而且

不影響代碼美觀。可以使得我們在編碼時可以更加專注于某一個具體的業務邏輯處理。

下面我們用責任鍊模式來優化一下代碼,首先建立一個Handler類 :

public abstract class Handler {
    protected Handler next;
    public void next(Handler next){ this.next = next;}
    public abstract void doHandler(Member member);
}           

我們分别建立非空校驗ValidateHandler類、登入校驗LoginHandler類和權限校驗AuthHandler

類 ,來看代碼ValidateHandler類 :

public class ValidateHandler extends Handler {
    public void doHandler(Member member) {
        if (StringUtils.isEmpty(member.getLoginName()) ||
                StringUtils.isEmpty(member.getLoginPass())) {
            System.out.println("使用者名和密碼為空");
            return;
        }
        System.out.println("使用者名和密碼不為空,可以往下執行");
        next.doHandler(member);
    }
}           

LoginHandler 類:

public class LoginHandler extends Handler {
    public void doHandler(Member member) {
        System.out.println("登入成功!");
        member.setRoleName("管理者");
        next.doHandler(member);
    }
}           

AuthHandler 類:

public class AuthHandler extends Handler {
    public void doHandler(Member member) {
        if (!"管理者".equals(member.getRoleName())) {
            System.out.println("您不是管理者,沒有操作權限");
            return;
        }
        System.out.println("允許操作");
    }
}           

接下來,修 改Memberservice中的代碼,其實我們隻需要将前面定義好的幾個Handler根據業務需求串聯起來,形成一條鍊即可

public class MemberService {

    public void login(String loginName, String loginPass) {
        Handler validateHandler = new ValidateHandler();
        Handler loginHandler = new LoginHandler();
        Handler authHandler = new AuthHandler();
        validateHandler.next(loginHandler);
        loginHandler.next(authHandler);
        validateHandler.doHandler(new Member(loginName, loginPass));
    }
}           

來看用戶端調用代碼:

public class Test {
    public static void main(String[] args) {
        MemberService memberService = new MemberService();
        memberService.login("tom","666");
    }
}           

其運作結果如下:

其實我們平時使用的很多權限校驗架構都是運用這樣一個原理,将各個次元的權限處了解耦之後再

串聯起來,各自隻處理各自相關的職責。如果職責與自己不相關則抛給鍊上的下一個Handler, 俗稱踢

皮球。

17.3.責任鍊模式和建造者模式結合使用

因為責任鍊模式具備鍊式結構,而上面代碼中,我們看到,負責組裝鍊式結構的角色是

MemberService ,當鍊式結構較長時,MemberService的工作會非常繁瑣,并且MemberService代

碼相對臃腫,且後續更改處理者或消息類型時,都必須在MemberService中進行修改,不符合開閉原 則。産生這些問題的原因就是因為鍊式結構的組裝過于複雜,而對于複雜結構的建立,很自然我們就會

想到建造者模式,使用建造者模式,我們完全可以MemberService指定的處理節點對象進行自動鍊式組裝,客戶隻需指定處理節點對象,其他任何事情都無需關心,并且客戶指定的處理節點對象順序不

同 ,構造出來的鍊式結構也随之不同。我們來改造一下,修改Handler的代碼:

public abstract class Handler<T> {
    protected Handler next;
    public void next(Handler next){ this.next = next;}

    public abstract void doHandler(Member member);

    public static class Builder<T>{
        private Handler<T> head;
        private Handler<T> tail;

        public Builder<T> addHandler(Handler handler){
           // do {
                if (this.head == null) {
                    this.head = this.tail = handler;
                    return this;
                }
                this.tail.next(handler);
                this.tail = handler;
           // }while (false);//真正架構中,如果是雙向連結清單,會判斷是否已經到了尾部
            return this;
        }

        public Handler<T> build(){
            return this.head;
        }
    }
}           

然 後 ,修改MemberService的代碼:

public class MemberService {
    public void login(String loginName,String loginPass){
        Handler.Builder builder = new Handler.Builder();
        builder.addHandler(new ValidateHandler())
               .addHandler(new LoginHandler())
               .addHandler(new AuthHandler());
        builder.build().doHandler(new Member(loginName,loginPass));
        //用過Netty的人,肯定見過
    }
}           

因為建造者模式要建構的是節點處理者,是以我們把Builder作為Handler的靜态内部類,并且因 為用戶端無需進行鍊式組裝,是以我們還可以把鍊式組裝方法next。方法設定為private,使 Handler

更加高内聚,代碼如下:

public abstract class Handler<T> {
    protected Handler chain;

    private void next(Handler handler) {
        this.chain = handler;
    }
}           

通過這個案例,小夥伴們應該已經感受到責任鍊和建造者結合的精 ITo

17.4.責任鍊模式在源碼中的展現

首先我們來看責任鍊模式在JDK中的應用,來看一個J2EE标準中非常常見的Filter類:

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    public void destroy();
}           

這個Filter接口的定義非常簡單,相當于責任鍊模型中的Handler抽象角色,那麼它是如何形成一 條責任鍊的呢?我來看另外一個類,其實在doFilte()方法的最後一個參數我們已經看到其類型是

FilterChain類 ,來看它的源碼:

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}           

FilterChain類中也隻定義了一個doFilter()方法,那麼它們是怎麼串聯成一個責任鍊的呢?實際上 J2EE隻是定義了一個規範,具體處理邏輯是由使用者自己來實作。我們來看一個Spring的實作

MockFilterChain類:

public class MockFilterChain implements FilterChain {

   @Nullable
   private ServletRequest request;

   @Nullable
   private ServletResponse response;

   private final List<Filter> filters;

   @Nullable
   private Iterator<Filter> iterator;

   public MockFilterChain() {
      this.filters = Collections.emptyList();
   }

   public MockFilterChain(Servlet servlet) {
      this.filters = initFilterList(servlet);
   }

   public MockFilterChain(Servlet servlet, Filter... filters) {
      Assert.notNull(filters, "filters cannot be null");
      Assert.noNullElements(filters, "filters cannot contain null values");
      this.filters = initFilterList(servlet, filters);
   }

   private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
      Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
      return Arrays.asList(allFilters);
   }

   @Nullable
   public ServletRequest getRequest() {
      return this.request;
   }

   @Nullable
   public ServletResponse getResponse() {
      return this.response;
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
      Assert.notNull(request, "Request must not be null");
      Assert.notNull(response, "Response must not be null");
      Assert.state(this.request == null, "This FilterChain has already been called!");

      if (this.iterator == null) {
         this.iterator = this.filters.iterator();
      }

      if (this.iterator.hasNext()) {
         Filter nextFilter = this.iterator.next();
         nextFilter.doFilter(request, response, this);
      }

      this.request = request;
      this.response = response;
   }

   public void reset() {
      this.request = null;
      this.response = null;
      this.iterator = null;
   }

   private static class ServletFilterProxy implements Filter {
      private final Servlet delegateServlet;

      private ServletFilterProxy(Servlet servlet) {
         Assert.notNull(servlet, "servlet cannot be null");
         this.delegateServlet = servlet;
      }

      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         this.delegateServlet.service(request, response);
      }

      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
      }

      @Override
      public void destroy() {
      }

      @Override
      public String toString() {
         return this.delegateServlet.toString();
      }
   }
}           

它把鍊條中的所有Filter放到List中 ,然後在調用doFilter。方法時循環疊代List , 也就是說List

中的Filter會順序執行。

再來看一個在Netty中非常經典的串行化處理Pipeline就采用了責任鍊設計模式。它底層采用雙向 連結清單的資料結構,将鍊上的各個處理器串聯起來。用戶端每一個請求的到來,Netty都認為Pipeline中 的所有的處理器都有機會處理它。是以,對于入棧的請求全部從頭節點開始往後傳播,一直傳播到尾節

點才會把消息釋放掉。來看一個Netty的責任處理器接口 ChannelHandler:

public interface ChannelHandler {
    // 當 handler被添加到真實的上下文中,并且準備處理事件時被調用
    // handler被添加進去的回調 
    void handlerAdded(ChannelHandlerContext var1) throws Exception;

    // 是 handler被移出的後的回調
    void handlerRemoved(ChannelHandlerContext var1) throws Exception;

    /** @deprecated */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;

    @Inherited
    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sharable {
    }
}           

Netty對責任處理接口做了更細粒度的劃分,處理器被分成了兩種,一種是入棧處理器

ChannellnboundHandler, 另一種是出棧處理器ChannelOutboundHandler, 這兩個接口都繼承自

ChannelHandler.而所有的處理器最終都在添加在Pipeline上。所 以 ,添加删除責任處理器的接口的

行為在Netty的 Channelpipeline中的進行了規定:

public interface ChannelPipeline extends ChannelInboundInvoker,ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {

    ChannelPipeline addFirst(String name, ChannelHandler handler);

    ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addLast(String name, ChannelHandler handler);

    ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);

    ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
    ...
}           

在預設是實作類中将所有的Handler都串成了一個連結清單:

public class DefaultChannelPipeline implements ChannelPipeline {

    static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);

    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    ...
}           

在 Pipeline中的任意一個節點,隻要我們不手動的往下傳播下去,這個事件就會終止傳播在目前節 點。對于入棧資料,預設會傳遞到尾節點進行回收。如果我們不進行下一步傳播,事件就會終止在目前

節點。對于出棧資料把資料寫會用戶端也意味看事件的終止。

當然在很多安全架構中也會大量使用責任鍊模式,比如Spring Security. Apache Shiro都會用到

設計模式中的責任鍊模式,感興趣的小夥伴可以嘗試自己去研究一下。

大部分架構中無論怎麼實作,所有的實作都是大同小異的。其實如果我們是站在設計者這個角度看

源碼的話,對我們學習源碼和編碼内功是非常非常有益處的,因為這樣,我們可以站在更高的角度來學

習優秀的思想,而不是鑽到某一個代碼細節裡邊。我們需要對所有的設計必須有一個宏觀概念,這樣學

習起來才更加輕松。

17.5.責任鍊模式的優缺點

優點:

​ 1、将請求與處了解耦;

​ 2、請求處理者(節點對象)隻需關注自己感興趣的請求進行處理即可,對于不感興趣的請求,直接轉

發給下一級節點對象;

​ 3、 具備鍊式傳遞處理請求功能,請求發送者無需知曉鍊路結構,隻需等待請求處理結果;

​ 4、 鍊路結構靈活,可以通過改變鍊路結構動态地新增或删減責任;

5、 易于擴充新的請求處理類(節點),符合開閉原則。

缺點:

​ 1、 責任鍊太長或者處理時間過長,會影響整體性能

​ 2、 如果節點對象存在循環引用時,會造成死循環,導緻系統崩潰;

17.6.作業

1、利用政策模式重構一段業務代碼。

使用政策模式實作一個電腦。

步驟 1

建立一個接口。

public interface Strategy {
   public int doOperation(int num1, int num2);
}           

步驟 2

建立實作接口的實體類。

// 加
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}

// 減
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

// 乘
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}           

步驟 3

建立 Context 類。

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}           

步驟 4

使用 Context 來檢視當它改變政策 Strategy 時的行為變化。

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubstract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}           

步驟 5

執行程式,輸出結果:

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50           

2、請描述出責任鍊模式和建造者模式結合應用的精髓。

責任鍊模式通過于建造者模式結合,可以有效控制連結清單順序。