前一陣子仔細看了一下Tomcat的Filter實現,才知道這個叫做責任鏈模式。正好藉此機會瞭解了一下。
責任鏈模式屬於行爲設計模式中的一種。
概念
責任鏈模式用來在設計時實現鬆耦合的一種方式,適用於當客戶端發送的請求對象需要進行一系列的處理的場景。然後,request對象會在責任鏈中來處理,在執行單元中判斷是否將request繼續發送到下一個執行單元來繼續處理。
JDK中的例子
JDK中就有使用責任鏈模式。java.util.logging.Filter
是一個例子。爲了更好的說明職責連的概念,try-catch
代碼塊是其中一個典型性的例子,try-catch
代碼塊中的每一個catch代碼塊就是類似於一個職責處理單元。
所以當try代碼塊中的代碼拋出了異常的時候,它會將異常交給第一個catch代碼塊來處理。如果第一個catch處理不了,異常將會繼續傳遞到下一個catch代碼塊來處理。如果最後一個catch代碼塊也無法處理的話,異常就會直接拋出給程序來處理。
責任鏈模式舉例
關於責任鏈的一個很不錯的例子就是找錢問題。當用戶輸入一個金額,而找錢問題就是根據這個金額返回找錢具體多少張100元,50元,20元,10元等等。
如果用戶輸入的金額不是10的倍數,找錢找不開,則直接拋出錯誤。下面我們可以通過責任鏈模式來解決這個問題。
當然,這個問題我們可以在一個程序中用幾個if-else就解決掉。但是隨着複雜性的增加,會將if-else的結構變得很複雜,而使用責任鏈模式,不同的責任之間耦合是很低的。所以無論是增加新的責任對象還是修改原來的責任對象,代價都會較小。
責任鏈模式-基類和接口
我們創建一個類Currency
會存儲我們需要分割的金額,之後這個類會被用來做責任鏈來處理。
package net.ethanpark.design.chainofresponsibility;
public class Currency {
private int amount;
public Currency(int amt){
this.amount=amt;
}
public int getAmount(){
return this.amount;
}
}
下面是找錢鏈的接口:
package net.ethanpark.design.chainofresponsibility;
public interface DispenseChain {
void setNextChain(DispenseChain nextChain);
void dispense(Currency cur);
}
setNextChain()
方法用來指向下一個找錢處理單元,實際執行動作的是dispense()
方法。
之後就是考慮實現不同的Dispense
來提供找錢服務:
package net.ethanpark.design.chainofresponsibility;
public class Dollar50Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 50){
int num = cur.getAmount()/50;
int remainder = cur.getAmount() % 50;
System.out.println("Dispensing "+num+" 50$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
package net.ethanpark.design.chainofresponsibility;
public class Dollar20Dispenser implements DispenseChain{
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 20){
int num = cur.getAmount()/20;
int remainder = cur.getAmount() % 20;
System.out.println("Dispensing "+num+" 20$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
package net.ethanpark.design.chainofresponsibility;
public class Dollar10Dispenser implements DispenseChain {
private DispenseChain chain;
@Override
public void setNextChain(DispenseChain nextChain) {
this.chain=nextChain;
}
@Override
public void dispense(Currency cur) {
if(cur.getAmount() >= 10){
int num = cur.getAmount()/10;
int remainder = cur.getAmount() % 10;
System.out.println("Dispensing "+num+" 10$ note");
if(remainder !=0) this.chain.dispense(new Currency(remainder));
}else{
this.chain.dispense(cur);
}
}
}
實現dispense
方法的實現就是其中的關鍵,我們可以發現每一個實現都是嘗試去處理基於amount的請求,而每一個Dispenser都只處理一部分職能。
如果當前執行單元完成對應的職責的話,就會將請求和處理扔到下一個執行單元,或者當前執行單元無法執行的話,就會直接將處理的執行交給下一個執行單元了。
創建責任鏈
這一步也是完成整個實現必不可少的一步,我們必須要謹慎的處理好執行鏈,否則,某些執行單元可能根本就無法拿到任何執行請求,更不用說去執行請求了。舉例來說,如果我們的第一個執行單元是Dollar10Dispenser
,第二個執行單元是Dollar20Dispenser
的話,那麼請求根本是不會交給第二個執行單元的。
package net.ethanpark.design.chainofresponsibility;
import java.util.Scanner;
public class ChangeDispenseChain {
private DispenseChain c1;
public ChangeDispenseChain() {
// initialize the chain
this.c1 = new Dollar50Dispenser();
DispenseChain c2 = new Dollar20Dispenser();
DispenseChain c3 = new Dollar10Dispenser();
// set the chain of responsibility
c1.setNextChain(c2);
c2.setNextChain(c3);
}
public static void main(String[] args) {
ChangeDispenseChain atmDispenser = new ChangeDispenseChain();
while (true) {
int amount = 0;
System.out.println("Enter amount to dispense");
Scanner input = new Scanner(System.in);
amount = input.nextInt();
if (amount % 10 != 0) {
System.out.println("Amount should be in multiple of 10s.");
return;
}
// process the request
atmDispenser.c1.dispense(new Currency(amount));
}
}
}
當我們運行上面的程序的時候,我們就能得到下面的輸出:
Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.
使用責任鏈模式的一些關鍵
- 客戶端不知道責任鏈的那一部分會處理請求,客戶端只知道將請求發送給責任鏈中的第一個對象。舉例來說,在我們的程序中,
ChangeDispenseChain
並不知道誰在處理請求來進行找零操作。 - 每個責任鏈中的執行單元都需要有自己的對於處理請求的實現,無論完成全部職能,還是完成部分職能,在完成之後都會講請求交給下一個執行單元。
- 每個職責連中的執行對象都需要有一個對象引用到下一個執行對象,這樣才能讓請求順着責任鏈一直執行。
- 創建責任鏈的過程很重要,否則將會有可能令請求無法發送到開發者需要其執行的處理單元上面。當然也有可能我們創建的責任鏈是無法完好的處理全部請求的。在上面的實現中,我們採用的方式是優先檢查所有的用戶請求,保證能夠被我們完全處理。當然我們可以實現一個默認的執行對象,令其處理所有不能處理的請求。無論哪一種當時都可以。
- 責任鏈模式可以很好的達到一定程度的鬆耦合,但是也會帶來一些其他的問題,比如會有大量的實現類來實現不同職責的處理單元,而且這些處理單元的執行順序是耦合的,這些都會帶來一定的問題。
責任鏈在其他知名代碼庫中也是有着廣泛的使用的,最常見的就是類似於Tomcat等Servlet容器中的Filter接口。很多基於Web的權限控制之類的操作,多數都是通過Filter來完成的。下面就是Servlet中的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(); }
一般比較複雜的場景,逐層遞進的情況下使用責任鏈模式比較適合。
前面也提到過,JDK中對應的例子則是
java.util.logging
中的Filter了:
public interface Filter { public boolean isLoggable(LogRecord record); }
開發者們可以參考其源碼來了解跟多關於責任鏈模式的內容。