title: “理解責任鏈模式”
url: “https://wsk1103.github.io/”
tags:
- 設計模式
是什麼
策略模式屬於行爲型模式。創建多個對象,使這些對象形成一條鏈,並沿着這條鏈傳遞請求,直到鏈上的某一個對象決定處理此請求。
優缺點
使程序結構更加靈活,擴展性更強。
優點
- 降低耦合度,客戶端不需要知道這個請求被誰處理了,而處理者也不需要知道各個處理者之間的傳遞關係。
- 擴展性強,新增處理者的時候,只需要繼承父處理者,然後重寫或者延用父處理者的業務邏輯方法。
缺點
- 請求會從鏈的頭部一直向下傳遞,直到有處理者處理,那麼有可能因爲鏈過長導致系統性能減低。
- 請求可能是遞歸傳遞的。
怎麼用
問題重現
現在需要爲從客戶端發起的請求做一個 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;
}
}
實現抽象類-開發,測試,生產
配合 spring 的 bean 管理,使所有實現類實例化並且單例化
開發環境
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()
最後
- spring 的過濾器 filter 就是使用了責任鏈模式- ApplicationFilterChain
參考:https://github.com/iluwatar/java-design-patterns/tree/master/chain