1. 程式人生 > >Java模式之責任鏈模式

Java模式之責任鏈模式

從請假開始談起

話說上次五一請假回去玩了一個多星期,期間回了一次老家,去了一趟武漢,接著又跑到了景德鎮,和廬山;玩的好不自在;但是想起來上次請假的經歷另我記憶猶新啊,算起來也是一波三折吧;

記得那時是4月20號,因為家裡出了點事情,急需回家,然後也快到五一了,就想出去玩幾天,因為之前加班積攢了不少的調休,所以想把調休給用了。記得當時我是先跟我們的部門經理說,我們部門經理跟我關係比較好,我就說胡哥,我想請假,回家有點事情,隨便出去玩幾天,胡哥說沒問題啊,隨便請。然後問我請幾天,我說八天。胡哥說:“八天不行啊,你要是請一天,我還能滿足你,但是一天以上的你就得找我們的老大,部門總監王總了!”,我說那我去找一下王總吧,王總說,你要請八天假啊,這個我是同意的,但是我只能處理三天內的假期,然後對我說,這事你得去找一下人事部門的老大讓他給你簽字,然後我找到了張總,張總說請假,沒問題啊,先填寫請假單吧,填完了請假單之後,張總一看,嗯,沒問題,不過你這個已經超過了一個星期了,你得CEO申請一下。最後我又找到了CEO,然後CEO說了一番鼓勵我的話,叫我處理完事情之後早點來上班;整個請假算是告一段落了;

在閻巨集博士的《Java與模式》一書中開頭是這樣描述責任鏈(Chain of Responsibility)模式的:

  責任鏈模式是一種物件的行為模式。在責任鏈模式裡,很多物件由每一個物件對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。


責任鏈模式的結構

  下面使用了一個責任鏈模式的最簡單的實現。

  責任鏈模式涉及到的角色如下所示:

  ●  抽象處理者(Handler)角色:定義出一個處理請求的介面。如果需要,介面可以定義 出一個方法以設定和返回對下家的引用。這個角色通常由一個Java抽象類或者Java介面實現。上圖中Handler類的聚合關係給出了具體子類對下家的引用,抽象方法handleRequest()規範了子類處理請求的操作。

  ●  具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇將請求處理掉,或者將請求傳給下家。由於具體處理者持有對下家的引用,因此,如果需要,具體處理者可以訪問下家。


什麼是鏈:

上面講到了我請假的流程和責任鏈模式的一種介紹,那麼我們可以理解一下,什麼是鏈;

1. 鏈是多個節點的有序集合。

2. 鏈的各節點可靈活拆分再重組。

再談請假:

先建立一個Handler用來處理我們的請假流程
/*
 * 處理人,負責處理請假申請
 */
public abstract class LeaveHandler {
	/*
	 * 直接後繼,用於傳遞請求
	 */
	protected LeaveHandler successor;

	public void setSuccessor(LeaveHandler successor) {
		this.successor = successor;
	}

	/*
	 * 處理請假申請
	 */
	public abstract void disposeLeave(int day);
}
好了現在建立我們的專案經理,他只能夠處理一天的假期;如果超過了一天我們就要向他的上級傳遞請求
/*
 * 專案經理可以批准一天的假期
 */
public class Lead extends LeaveHandler {

	@Override
	public void disposeLeave(int day) {
		if (day <=  1) {
			System.out.println("我叫做胡經理,我可以處理" + day + "的假期");
		} else {
			// 如果他處理不了就向上傳遞請求
			successor.disposeLeave(day);
		}

	}

}

/*
 * 技術總監可以批准三天的假期
 */
public class CTO extends LeaveHandler{
	@Override
	public void disposeLeave(int day) {
		if (day <= 3) {
			System.out.println("我叫做王總監,我可以處理" + day + "內的假期");
		} else {
			// 如果他處理不了就向上傳遞請求
			successor.disposeLeave(day);
		}

	}

}

/*
 * 人事部門老大可以批准一個星期內的假期
 */
public class HrBoos extends LeaveHandler{
	@Override
	public void disposeLeave(int day) {
		if (day <= 5) {
			System.out.println("我叫做張老大,我可以處理" + day + "內的假期");
		} else {
			// 如果他處理不了就向上傳遞請求
			successor.disposeLeave(day);
		}

	}

}

/*
 * 老闆,只要他同意,你可以無限期休假
 */
public class CEO extends LeaveHandler {
	@Override
	public void disposeLeave(int day) {
		//因為這裡所有的假期他都可以處理所以沒有判斷
	System.out.println("我叫做CEO,我可以處理" + day + "的假期");
	}

}

public class LeaveHandlerFactory {
	
	
	/*/
	 * 建立工廠方法
	 */
	public static LeaveHandler createHandler(){
		LeaveHandler lead=new Lead();
		LeaveHandler cto=new CTO();
		LeaveHandler hrBoos=new HrBoos();
		LeaveHandler ceo=new CEO();
		
		lead.setSuccessor(cto);
		cto.setSuccessor(hrBoos);
		hrBoos.setSuccessor(ceo);
		return lead;
	}

最後編寫測試用例
public class Test {
	private LeaveHandler handler;
	
	public LeaveHandler getHandler() {
		return handler;
	}

	public void setHandler(LeaveHandler handler) {
		this.handler = handler;
	}
	public void requestDiscount(int day){
		handler.disposeLeave(day);
	}
	

	public static void main(String[] args) {
		Test test=new Test();
		test.setHandler(LeaveHandlerFactory.createHandler());
		test.requestDiscount(8);
		
	}

}

責任鏈模式的優缺點:

模式的有點就在於實現瞭解耦,符合開閉原則,在這裡面呼叫者不需要知道具體的傳遞過程,他只需要知道最終的結果被處理了。而且這個連結串列的結構可以被靈活的更改重組。

但是它的缺點也是很明顯的,首先從效能上說起,一個是呼叫時間,如果連結串列在最開始被處理了還好,萬一連結串列跑到了最後一個才被處理,那麼他的呼叫時間肯定會比不適用責任鏈模式的效率要低一些;第二是記憶體的問題,我們會構造出很多的連結串列節點物件,但是有些物件在我們的應用場景中是不會用到的,所以大大的消耗了我們的記憶體;

純的與不純的責任鏈模式
  一個純的責任鏈模式要求一個具體的處理者物件只能在兩個行為中選擇一個:一是承擔責任,而是把責任推給下家。不允許出現某一個具體處理者物件在承擔了一部分責任後又 把責任向下傳的情況。
  在一個純的責任鏈模式裡面,一個請求必須被某一個處理者物件所接收;在一個不純的責任鏈模式裡面,一個請求可以最終不被任何接收端物件所接收。
  純的責任鏈模式的實際例子很難找到,一般看到的例子均是不純的責任鏈模式的實現。有些人認為不純的責任鏈根本不是責任鏈模式,這也許是有道理的。但是在實際的系統裡,純的責任鏈很難找到。如果堅持責任鏈不純便不是責任鏈模式,那麼責任鏈模式便不會有太大意義了。
我們經常用到的責任鏈模式:

在早期的java中使用到責任鏈的有AWT這個專案;


後來改為了觀察者模式,我想這也是效能的問題;

還有我們的


異常處理機制,我們捕獲了一個異常之後可以選擇自己去處理掉這個異常,也可以選擇往上丟擲異常;

還有前端的


DOM樹也是一樣,當我們對TD綁定了一個點選事件並且觸發之後,它會從這個document去尋找,如果沒有找到就接著往下找,直到找到了這個TD,再返回這個點選事件;

當然還有我們比較熟悉的Filter過濾器

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

    public void destroy() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

它可以自己去處理這個請求,也可以交給下一個攔截器去處理;

職責鏈總結

職責鏈模式不能亂用,否則非常容易變成因為模式而模式的反例。

如果有更好的模式可以代替我們的責任鏈模式的話,可以考慮去替代它。