定義
責(zé)任鏈模式(Chain of Responsibility)是多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系.將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有對(duì)象能夠處理.
場(chǎng)景
員工想要請(qǐng)假,在OA系統(tǒng)中需要審批.
對(duì)于不同的請(qǐng)假天數(shù),審批人是不一樣的.小于3天,組長(zhǎng)審批,大于三天小于7天,主管審批,大于7天小于15天CTO審批.
典型實(shí)現(xiàn)
public Response handle(Request req) {
if (req.day < 0 || req.day > 15) {
throw new IllegalArgumentException();
}
if (req.day <= 3) {
return new Leader().handle(req);
} else if (req.day <= 7) {
return new Director().handle(req);
} else {
return new CTO().handle(req);
}
}
實(shí)際場(chǎng)景可能要比這個(gè)復(fù)雜一些,比如Leader,Director,CTO可能是通過(guò)注入而不是new的.
目前的類圖:
優(yōu)點(diǎn):
- 代碼清晰明了.
缺點(diǎn):
- 耦合度高,客戶端代碼依賴所有處理類. 如果后續(xù)想要繼續(xù)添加處理類,就需要繼續(xù)添加else if.
- 順序也是定死的.如果要改順序.就必須重新修改.
改進(jìn)[責(zé)任鏈]
既然我們的所有處理類都實(shí)現(xiàn)了IHandler接口.那我們的執(zhí)行流程在編譯層面就是標(biāo)準(zhǔn)化的.那我們能不能通過(guò)鏈接各個(gè)處理類的方式,讓Client依賴的實(shí)現(xiàn)最小呢?當(dāng)然可以.和鏈表類似.我們可以構(gòu)造一個(gè)處理器鏈.只需要在IHandler里添加setNext()方法即可.
改進(jìn)后代碼
IHandler實(shí)現(xiàn)類:
class Leader implements IHandler {
private IHandler next;
public Response handle(Request req) {
if (req.getDay() <= 3) {
return new Response("Leader audit");
}
return this.next.handle(req);
}
@Override
public void setNext(IHandler handler) {
this.next = handler;
}
}
其他類似,就不貼了.
業(yè)務(wù)代碼:
public Response handle(Request req) {
if (req.day < 0 || req.day > 15) {
throw new IllegalArgumentException();
}
return handler.handle(req);
}
客戶端代碼:
public static void main(String[] args) {
IHandler handler = new Leader();
IHandler director = new Director();
IHandler cto = new CTO();
handler.setNext(director);
director.setNext(cto);
CORDemo01 demo = new CORDemo01();
demo.handler = handler;
Response response = demo.handle(new Request(6));
System.out.println(response.getMessage());
}
改進(jìn)后的類圖
優(yōu)點(diǎn)
- 不再依賴所有實(shí)現(xiàn).而是只依賴第一個(gè)實(shí)現(xiàn).而其他的實(shí)現(xiàn)通過(guò)鏈接的方式進(jìn)行組裝.
- 我們已經(jīng)可以通過(guò)某些方式動(dòng)態(tài)的組裝我們的鏈了.
總結(jié)
純粹的責(zé)任鏈模式這樣就介紹完了.
從傳統(tǒng)的編程方式過(guò)度到責(zé)任鏈模式.實(shí)際上還是很容易理解了.為的就是減少調(diào)用類與處理類之間的耦合.
另外值得注意的是,純粹的責(zé)任鏈模式,只有一個(gè)處理器進(jìn)行處理.而其他的并不參與執(zhí)行.
而我們?cè)趯?shí)際使用的場(chǎng)景中,比如過(guò)濾器,則是多個(gè)處理器都會(huì)參與執(zhí)行.
下面就討論一下.責(zé)任鏈的幾個(gè)變種.
責(zé)任鏈模式列表方式實(shí)現(xiàn)
我們看到純的責(zé)任鏈模式中,使用了鏈表的形式.并且暴露了第一個(gè)執(zhí)行單元.
這種鏈表的方式在組裝的時(shí)候如果有很多個(gè)節(jié)點(diǎn),將十分繁瑣.對(duì)于這一部分,有一種常見的優(yōu)化方案.
是將所有的處理器按順序放到一個(gè)List中進(jìn)行處理.代碼如下
IHandler實(shí)現(xiàn)類:
class Leader implements IHandler {
public Response handle(Request req) {
if (req.getDay() <= 3) {
return new Response("Leader audit");
}
return null;
}
}
其他類似,就不貼了.
HandlerChain
class HandlerChain {
private List<IHandler> processList = Lists.newArrayList();
public HandlerChain addHandler(IHandler handler) {
if (handler != null) {
processList.add(handler);
}
// 鏈?zhǔn)秸{(diào)用.
return this;
}
public void setProcessList(List<IHandler> processList) {
this.processList = processList;
}
public Response process(Request req) {
for (IHandler handler : processList) {
Response response = handler.handle(req);
if (response == null) {
continue;
}
return response;
}
throw new IllegalArgumentException();
}
}
優(yōu)點(diǎn)
- 通過(guò)這種方式,我們可以較為輕松的組裝我們的處理鏈.
- 通過(guò)setProcessList方法.我們也可以動(dòng)態(tài)的更改處理流程.
不純粹的責(zé)任鏈
過(guò)濾器
不純粹的責(zé)任鏈?zhǔn)侵?進(jìn)入鏈之后,并不是只有一個(gè)處理器才能執(zhí)行.而是所有的處理器都可能參與執(zhí)行.
典型的場(chǎng)景就是過(guò)濾器
下面我們簡(jiǎn)單實(shí)現(xiàn)一個(gè)過(guò)濾器.我們有一個(gè)List的String.我們將其中長(zhǎng)度大于1,首字母是w的字符串都挑選出來(lái).如果用過(guò)Java8的朋友肯定知道,使用stream表達(dá)式,filter就可以很容易的實(shí)現(xiàn)這個(gè)需求.但是實(shí)際場(chǎng)景中,可能要比這個(gè)復(fù)雜的多.這里只是舉個(gè)簡(jiǎn)單的例子.來(lái)說(shuō)明這種不純粹的責(zé)任鏈的實(shí)現(xiàn).實(shí)現(xiàn)方式采取兩種.
傳統(tǒng)方式.利用Java8 Function的antThen組裝多個(gè)Function的方式實(shí)現(xiàn)過(guò)濾器.
-
傳統(tǒng)方式
接口類
public interface IFilter { List<String> filter(List<String> toFilter); }
組裝類
class FilterChain { private List<IFilter> filterList; public List<String> process(List<String> toFilter) { for (IFilter filter : filterList) { toFilter = filter.filter(toFilter); } return toFilter; } }
兩個(gè)實(shí)現(xiàn)類
class LengthFilter implements IFilter { @Override public List<String> filter(List<String> toFilter) { List<String> result = Lists.newArrayList(); for (String str : toFilter) { if (StringUtils.isNotBlank(str) && str.length() > 1) { result.add(str); } } return result; } } class StartCharFilter implements IFilter { @Override public List<String> filter(List<String> toFilter) { List<String> result = Lists.newArrayList(); for (String str : toFilter) { if (StringUtils.isNotBlank(str) && str.startsWith("w")) { result.add(str); } } return result; } }
值得注意的是有很多模板代碼.這些模板代碼在傳統(tǒng)方式下,可以通過(guò)模板模式進(jìn)行簡(jiǎn)化.
-
Java8 Function方式
public class HandlerChain { private static UnaryOperator<List<String>> filterByLength = (List<String> text) -> text.stream().filter(item -> item.length() > 1).collect(Collectors.toList()); private static UnaryOperator<List<String>> spellCheckerProcessing = (List<String> text) -> text.stream().filter(item -> item.startsWith("w")).collect(Collectors.toList()); private static Function<List<String>, List<String>> filterChain = filterByLength.andThen(spellCheckerProcessing); public static List<String> filter(List<String> input) { return filterChain.apply(input); } }
當(dāng)然在這個(gè)需求下,你也可以直接使用stream.將兩個(gè)filter連接起來(lái).
但是如果你考慮純粹的責(zé)任鏈模式.上面的Java8 Funciton的方式.確實(shí)可以節(jié)省很多代碼和類.
攔截器
Servlet,Struts2,Spring MVC,都有攔截器.而這些攔截器的實(shí)現(xiàn).則也是不純粹的責(zé)任鏈的一種.因?yàn)樗械臄r截器都會(huì)執(zhí)行一遍.如果確認(rèn)攔截,則直接返回.如果通過(guò),則繼續(xù)執(zhí)行.看起來(lái)和過(guò)濾器很像.
但是攔截器的功能不止于此,攔截器不僅支持preAction操作,還支持postAction操作.我們想要討論的就是這種設(shè)計(jì)是如何編碼實(shí)現(xiàn)的.
接口定義
public interface IInterceptor {
boolean before();
void after();
}
攔截器與目標(biāo)對(duì)象的組裝類
public class MainExecuteProxy {
private Object target;
private List<IInterceptor> interceptorList = Lists.newArrayList();
public MainExecuteProxy addInterceptor(IInterceptor interceptor) {
interceptorList.add(interceptor);
return this;
}
public String execute() {
return process(interceptorList.iterator(), target);
}
private String process(Iterator<IInterceptor> interceptorIterator, Object target) {
if (interceptorIterator.hasNext()) {
IInterceptor interceptor = interceptorIterator.next();
boolean before = interceptor.before();
if (!before) {
return "執(zhí)行失敗";
}
String res = process(interceptorIterator, target);
interceptor.after();
return res;
} else {
// do action.
System.out.println("real action");
return "執(zhí)行成功";
}
}
}
代碼分析
MainExecuteProxy類里需要有一個(gè)目標(biāo)對(duì)象,用來(lái)執(zhí)行目標(biāo)方法.而interceptorList就是我們之前講到的責(zé)任鏈.那這種攔截是怎么實(shí)現(xiàn)的呢.主要的邏輯就是process方法.我們的邏輯很簡(jiǎn)單.就是當(dāng)我們的所有before方法都為true的時(shí)候執(zhí)行目標(biāo)對(duì)象的方法.當(dāng)有一個(gè)失敗的時(shí)候,就立馬攔截.而在攔截并且執(zhí)行完目標(biāo)方法后,我們需要在一個(gè)一個(gè)的反向執(zhí)行after方法.因?yàn)閎efore/after是配對(duì)的.
為了達(dá)到這種目的,我們使用了遞歸.原因就是遞歸是可以保留遞歸現(xiàn)場(chǎng)的.所以的方法在調(diào)用的時(shí)候進(jìn)入到方法棧,先進(jìn)去的在下面,后進(jìn)去的在上面.所以當(dāng)我們?cè)趫?zhí)行完目標(biāo)方法一層一層返回的時(shí)候,也是后調(diào)用的先執(zhí)行after.達(dá)到了效果.
Client代碼
public class Main {
public static void main(String[] args) {
MainExecuteProxy executeProxy = new MainExecuteProxy();
String result = executeProxy.addInterceptor(new IInterceptor() {
@Override
public boolean before() {
System.out.println("aaa");
return true;
}
@Override
public void after() {
System.out.println("aaa");
}
}).addInterceptor(new IInterceptor() {
@Override
public boolean before() {
System.out.println("bbb");
return false;
}
@Override
public void after() {
System.out.println("bbb");
}
}).execute();
System.out.println(result);
}
}
執(zhí)行結(jié)果:
aaa
bbb
aaa
執(zhí)行失敗
先進(jìn)入攔截器一的before,輸出aaa,返回true,繼續(xù)執(zhí)行
進(jìn)入攔截器二的before,輸出bbb,返回false.停止繼續(xù)執(zhí)行,返回執(zhí)行失敗.
回到上一層的代碼.繼續(xù)執(zhí)行攔截器一的after.輸出aaa.然后返回.
和預(yù)期的相符.
總結(jié)
攔截器模式,無(wú)論純粹或變種,在實(shí)際開發(fā)和框架中都會(huì)時(shí)常看到,需要我們掌握.并且在合適的時(shí)機(jī)予以使用.
參考資料
- https://zhuanlan.zhihu.com/p/24737592
- 設(shè)計(jì)模式之禪