設計模式系列之「責任鏈模式」

YoungManSter發表於2019-02-28

“長安回望繡成堆,山頂千門次第開。一騎紅塵妃子笑,無人知是荔枝來”。杜牧的《過華清宮》流傳千古,楊貴妃喜食荔枝也成為眾所周知的事情。楊貴妃吃的荔枝必須在採摘後的幾天內送到,如果超過了四五天,荔枝就會腐爛,在古代路途遙遠和交通不便成為了致命傷,為了讓楊貴妃吃到新鮮的荔枝而不勞民傷財,小Y決定讓楊貴妃和現代物流來一個偶遇。

楊貴妃: 大王,臣妾想吃新鮮可口的荔枝(一陣撒嬌聲,各位各自想象)。

唐玄宗: “ok!No趴笨”。然後馬上召來路人甲,讓路人甲無論如何都要在三天之內把荔枝送來,不然以死謝罪。

朝中大臣紛紛幸災樂禍,覺得路人甲肯定會死無葬身之地的。但是路人甲胸有成竹的領命而去 (路人甲之所以這麼信心十足,是因為路人甲曾得神祕高人Y指點,說命中必有一劫,只有按照幾千年後的現代做法建一套有效的物流方法方可避過此劫,所以路人甲就按照神祕高人Y指點祕密在全國各地建造一個物流據點,然後通過特製的千里箭進行通訊)

回到家中的路人甲裡面把荔枝的貨物需求綁在千里箭中並射向了最近的據點bj倉,bj倉的負責人收到之後,發現這個倉中沒有,然後又把這個需求通過千里箭射向了js倉,但最終在gd倉找到了貨物,再通過特製的飛鷂在三天內把貨物送到楊貴妃所在的地方。

楊貴妃吃到了新鮮的荔枝之後,心情大悅,路人甲也因此仕途暢通無阻。

本故事終,故事純屬小Y瞎掰,哇哈哈哈。

一、路人甲如何建立行之有效的物流系統

神祕高人Y把幾千年後的發達物流模式傳授給了路人甲,要求在全國各大城市建立據點。

1.粗糙期

①荔枝的介面

public interface ILiZhi {
	//得到荔枝的品種
	public int getType();
	//每個倉庫中得到荔枝訂單的請求
	public String getRequest();
}
複製程式碼

②定義荔枝的具體類

public class LiZhi implements ILiZhi {

	//荔枝的種類,1代表糯米餈 2代表桂味 3代表槐味
	private int type=0;
	//荔枝的訂單需求
	private String request;

	public LiZhi(int type,String request) {
		this.type = type;
		this.request=request;
	}

	@Override
	public int getType() {
		return type;
	}

	@Override
	public String getRequest() {
		return request;
	}
}
複製程式碼

③定義一個據點介面

public interface ILiZhiHouse {
	//處理訂單需求
	public void dealWithRequest(ILiZhi iLiZhi);
}
複製程式碼

④bj據點,只有糯米餈荔枝

public class BJHouse implements ILiZhiHouse {
	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
		System.out.println("訂單需求:"+iLiZhi.getRequest());
		System.out.println("貨物已由bj據點火速送出");
	}
}
複製程式碼

⑤js據點,只有桂味荔枝

public class JSHouse implements ILiZhiHouse {
	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
	System.out.println("訂單需求:"+iLiZhi.getRequest());
	System.out.println("貨物已由js據點火速送出");
	}
}
複製程式碼

⑥gd據點,只有槐味荔枝

public class GDHouse implements ILiZhiHouse {
	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
	System.out.println("訂單需求:"+iLiZhi.getRequest());
	System.out.println("貨物已由gd據點火速送出");
	}
}
複製程式碼

物流系統建好了,現在就等楊貴妃下達荔枝需求了。

⑦楊貴妃一聲令下,要求三天之內把糯米餈、桂味、槐味全部送達

public class Client {

	public static void main(String[] args) {
		//1代表糯米餈 2代表桂味 3代表槐味

		//路人甲根據楊貴妃的要求組裝需求
		List<ILiZhi> liZhis=new ArrayList<>();
		liZhis.add(new LiZhi(1,"需要糯米餈30斤"));
		liZhis.add(new LiZhi(2,"需要桂味30斤"));
		liZhis.add(new LiZhi(3,"需要槐味30斤"));

		//定義三個據點
		BJHouse bjHouse=new BJHouse();
		JSHouse jsHouse=new JSHouse();
		GDHouse gdHouse=new GDHouse();

		for(ILiZhi liZhi:liZhis){
			if(liZhi.getType()==1){
				bjHouse.dealWithRequest(liZhi);
			}else if(liZhi.getType()==2){
				jsHouse.dealWithRequest(liZhi);
			}else if(liZhi.getType()==3){
				gdHouse.dealWithRequest(liZhi);
			}else{
			System.out.println("沒有建立這樣的據點,只能坐等死了。");
			}
		}
	}
}
複製程式碼

⑧執行結果

訂單需求:需要糯米餈30斤
貨物已由bj據點火速送出

訂單需求:需要桂味30斤
貨物已由js據點火速送出

訂單需求:需要槐味30斤
貨物已由gd據點火速送出
複製程式碼

這個就是早期路人甲根據神祕高人Y方法建立的一套物流系統,路人甲覺得這套系統已經完美無缺了,但是給神祕高人Y一看,立馬指出了一下問題:

  • Client的程式碼臃腫,不同的荔枝品種就需要增加一個判斷,隨著品種的增多會造成if…else的判斷越來越多,很容易出現混亂,可讀性不強。

  • 耦合過重,違背開閉原則。

  • 異常處理不合理。如果路人甲把糯米餈的訂單直接發到了配送桂味的據點,那麼桂味據點無法處理這個訂單呀,不處理的話那麼路人甲就要坐等死了。

路人甲一看分析出這麼多問題,立馬根據需求設計了一個新的物流系統:訂單下來,由近到遠的據點一個個分配訂單,比如說桂味訂單到了bj據點,bj據點處理不了,只能把這個訂單繼續往下一個據點傳遞,到達js據點能夠處理就進行配送,不需要再外下一個據點進行傳遞了,即必然有一個唯一的據點給出唯一的答覆

2.完善期

(1)據點UML
設計模式系列之「責任鏈模式」
(2)改進後的物流系統程式碼

①荔枝的介面

public interface ILiZhi {
	//得到荔枝的品種
	public int getType();
	//每個倉庫中得到荔枝訂單的請求
	public String getRequest();
}
複製程式碼

②定義荔枝的具體類

public class LiZhi implements ILiZhi {

	//荔枝的種類,1代表糯米餈 2代表桂味 3代表槐味
	private int type=0;
	//荔枝的訂單需求
	private String request;

	public LiZhi(int type,String request) {
		this.type = type;
		this.request=request;
	}

	@Override
	public int getType() {
		return type;
	}

	@Override
	public String getRequest() {
		return request;
	}
}
複製程式碼

③定義一個抽象據點類

public abstract class ILiZhiHouse {

	//定義三種型別
	public final static int BJ_TYPE_REQUEST = 1;
	public final static int JS_TYPE_REQUEST = 2;
	public final static int GD_TYPE_REQUEST = 3;	

	//責任傳遞,下一個人責任人是誰
	private ILiZhiHouse liZhiHouse;
	//能處理的訂單
	private int type =0;

	public ILiZhiHouse(int type) {
		this.type = type;
	}

	//分發請求
	public void handleMessage(ILiZhi iLiZhi){
		if(iLiZhi.getType()==type){
			this.dealWithRequest(iLiZhi);
		}else{
			if(this.liZhiHouse!=null){
				this.liZhiHouse.handleMessage(iLiZhi);
			}else{
				System.out.println("沒有建立這樣的據點,只能坐等死了。");
			}
		}

	}
	//設定下一個據點是哪個
	public void setNext(ILiZhiHouse liZhiHouse){
		this.liZhiHouse=liZhiHouse;
	}
	//處理訂單需求
	public abstract void dealWithRequest(ILiZhi iLiZhi);
}
複製程式碼

④bj據點,只有糯米餈荔枝

public class BJHouse extends ILiZhiHouse {

	public BJHouse() {
		super(BJ_TYPE_REQUEST);
	}

	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
		System.out.println("訂單需求:"+iLiZhi.getRequest());
		System.out.println("貨物已由bj據點火速送出");
	}
}
複製程式碼

⑤js據點,只有桂味荔枝

public class JSHouse extends ILiZhiHouse {

	public JSHouse() {
		super(JS_TYPE_REQUEST);
	}

	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
		System.out.println("訂單需求:"+iLiZhi.getRequest());
		System.out.println("獲取已由js據點火速送出");
	}
}
複製程式碼

⑥gd據點,只有槐味荔枝

public class GDHouse extends ILiZhiHouse {
	public GDHouse() {
		super(GD_TYPE_REQUEST);
	}

	@Override
	public void dealWithRequest(ILiZhi iLiZhi) {
		System.out.println("訂單需求:"+iLiZhi.getRequest());
		System.out.println("獲取已由gd據點火速送出");
	}
}
複製程式碼

⑦配送荔枝

public class Client {

	public static void main(String[] args) {
		//1代表糯米餈 2代表桂味 3代表槐味

		//路人甲根據楊貴妃的要求組裝需求
		List<ILiZhi> liZhis=new ArrayList<>();
		liZhis.add(new LiZhi(ILiZhiHouse.BJ_TYPE_REQUEST,"需要糯米餈30斤"));
		liZhis.add(new LiZhi(ILiZhiHouse.JS_TYPE_REQUEST,"需要桂味30斤"));
		liZhis.add(new LiZhi(ILiZhiHouse.GD_TYPE_REQUEST,"需要槐味30斤"));

		//定義三個據點
		BJHouse bjHouse=new BJHouse();
		JSHouse jsHouse=new JSHouse();
		GDHouse gdHouse=new GDHouse();
		//根據據點遠近設定順序
		bjHouse.setNext(jsHouse);
		jsHouse.setNext(gdHouse);

		for(ILiZhi liZhi:liZhis){
			bjHouse.handleMessage(liZhi);
		}
	}
}
複製程式碼

⑧執行結果

訂單需求:需要糯米餈30斤
貨物已由bj據點火速送出

訂單需求:需要桂味30斤
貨物已由js據點火速送出

訂單需求:需要槐味30斤
貨物已由gd據點火速送出
複製程式碼

業務呼叫類Client也不用去做判斷到底是需要誰去處理,而且ILiZhiHouse抽象類的子類可以繼續增加下去,只需要擴充套件傳遞鏈而已,呼叫類可以不用瞭解變化過程,甚至是誰在處理這個請求都不用知道。

二、基本概念

1.定義

使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。

2.責任鏈的重點

責任鏈模式的重點是在“鏈”上,由一條鏈去處理相似的請求在鏈中決定誰來處理這個請求,並返回相應的結果。

3.角色介紹

設計模式系列之「責任鏈模式」
  • Handler
    抽象的處理者實現三個職責:一是定義一個請求的處理方法handleMessage;二是定義一個鏈的編排方法setNext,設定下一個處理者;三是定義了具體的請求者必須實現的兩個方法:定義自己能夠處理的級別和具體的處理任務。

  • ConcreteHandler
    責任鏈模式的核心在“鏈”上,“鏈”是由多個處理者ConcreteHandler組成的。具體的請求者必須實現的兩個方法:定義自己能夠處理的級別和具體的處理任務。

三、責任鏈模式優缺點

1.優點

  • 責任鏈模式非常顯著的優點是將請求和處理分開。請求者可以不用知道是誰處理的,處理者可以不用知道請求的全貌,,兩者解耦,提高系統的靈活性

2.缺點

  • 效能問題。每個請求都是從鏈頭遍歷到鏈尾,特別是在鏈比較長的時候,效能是一個非常大的問題

  • 除錯不很方便,特別是鏈條比較長,環節比較多的時候,由於採用了類似遞迴的方式,除錯的時候邏輯可能比較複雜。

四、總結

鏈中節點數量需要控制,避免出現超長鏈的情況,一般的做法是在Handler中設定一個最大節點數量,在setNext方法中判斷是否已經是超過其閾值,超過則不允許該鏈建立,避免無意識地破壞系統效能。

Android技術交流吧

相關文章