理解責任鏈模式


title: “理解責任鏈模式”

url: “https://wsk1103.github.io/

tags:

  • 設計模式

是什麼

策略模式屬於行爲型模式。創建多個對象,使這些對象形成一條鏈,並沿着這條鏈傳遞請求,直到鏈上的某一個對象決定處理此請求。

優缺點

使程序結構更加靈活,擴展性更強。

優點

  1. 降低耦合度,客戶端不需要知道這個請求被誰處理了,而處理者也不需要知道各個處理者之間的傳遞關係。
  2. 擴展性強,新增處理者的時候,只需要繼承父處理者,然後重寫或者延用父處理者的業務邏輯方法。

缺點

  1. 請求會從鏈的頭部一直向下傳遞,直到有處理者處理,那麼有可能因爲鏈過長導致系統性能減低。
  2. 請求可能是遞歸傳遞的。

怎麼用

問題重現

現在需要爲從客戶端發起的請求做一個 referer 的防盜鏈,這個防盜鏈分爲本地、測試和生產。每一種環境 referer 處理的正則表達式都不一樣。

一般想法的話,是在一個類中寫出所有的 正則表達式 ,然後循環判斷。但是當新增一個環境的時候,就需要修改這個類,這樣子 不符合開閉原則

一般代碼實現:

package com.wsk.gateway.config;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @description 描述
 */
public class Simple {

    private static final Pattern DEV = Pattern.compile("^https?://(localhost|127.0.0.1)(/|$)");

    private static final Pattern TEST = Pattern.compile("^https?://(test.wsk1103.com|192.168.1.1)(/|$)");

    private static final Pattern REP = Pattern.compile("^https?://(wsk1103.com|192.168.2.1)(/|$)");

    public static boolean doChain(String referer) {
        return DEV.matcher(referer).find() || TEST.matcher(referer).find() || REP.matcher(referer).find();
    }

}

當新增一個環境的時候,就需要改動這個代碼。

使用責任鏈模式

設計抽象類

package com.wsk.gateway.config;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 抽象的處理器
 */
public abstract class AbstractRefererAction implements Comparable{
    /**
     * 鏈的優先級
     */
    private int num;

    /**
     * 正則表達式
     */
    private Pattern pattern;

    protected AbstractRefererAction(int num, Pattern pattern) {
        this.num = num;
        this.pattern = pattern;
    }

    /**
     * 處理邏輯,子類只需要提供正則表達式和優先級即可
     * @param referer
     * @return
     */
    public boolean doChain(String referer) {
        if (pattern == null) {
            return false;
        }
        return pattern.matcher(referer).find();
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof AbstractRefererAction) {
            return Integer.compare(this.num, ((AbstractRefererAction) o).getNum());
        } else {
            throw new IllegalArgumentException();
        }
    }

    public int getNum() {
        return this.num;
    }

}

實現抽象類-開發,測試,生產

配合 springbean 管理,使所有實現類實例化並且單例化

開發環境

package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 開發環境
 */
@Service
public class DevRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(localhost|127.0.0.1)(/|$)");

    private DevRefererAction() {
        super(999, PATTERN);
    }
}

測試環境

package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 描述
 */
@Service
public class TestRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(test.wsk1103.com|192.168.1.1)(/|$)");

    private TestRefererAction() {
        super(100, PATTERN);
    }

}

生產環境

package com.wsk.gateway.config;

import org.springframework.stereotype.Service;

import java.util.regex.Pattern;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description 描述
 */
@Service
public class RepRefererAction extends AbstractRefererAction {

    private static final Pattern PATTERN = Pattern.compile("^https?://(wsk1103.com|192.168.2.1)(/|$)");

    private RepRefererAction() {
        super(-999, PATTERN);
    }

}

spring 全局bean管理

package com.wsk.gateway.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author wsk1103
 * @date 2019/6/24
 * @description spring 全局bean管理
 */
public class SpringContext implements ApplicationContextAware {

    /**
     * Spring應用上下文環境
     */
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContext.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

雖然代碼看起來是增多了,但是增強了擴展性。後續的開發不需要修改原來的代碼,只需要實現相應的接口並且定義正則表達式即可。

還有一種更方便的實現,使用配置中心,例如攜程的 Apollo 等等,然後將所有正則表達式發佈到服務器,並且解析實現。

java中的應用

java.util.logging.Logger#log()
Apache Commons Chain
javax.servlet.Filter#doFilter()

最後

  1. spring 的過濾器 filter 就是使用了責任鏈模式- ApplicationFilterChain

參考:https://github.com/iluwatar/java-design-patterns/tree/master/chain

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章