【設計模式】行為型 - 職責鏈
【前提 & 場景】
寫業務邏輯的時候(和銀行互動或者金融相關業務),小編常會遇到這種情況,如圖:
流程比較複雜,訂單處理節點會很多,按照傳統的設計,程式碼的條理性、可讀性都會很差,經常會出現很多if else判斷,不去好好設計,很容易被後人視為“壞味道” 。而“職責鏈模式”恰恰能很好解決這個問題,詳情往下看。
【簡介】
1.概念
職責鏈 - 使多個物件(處理者)都有機會處理請求,從而減緩了請求的傳送者和(每個)接受者之間的耦合關係。將這個(處理者)物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理他為止。
2.UML
【理解】
1)場景:1.業務流程非常複雜,2.通常一個請求者,多個處理者,處理過程有順序。
2)通俗來講,職責鏈模式,就是把處理者重新做了一層封裝,因為處理物件的有序性需求,Handler的實現往往有2個必要的方法,其一是對處理者的節點順序處理,當前節點處理完成之後使其能夠到下一個節點處理。其二是處理當前節點所需要的業務邏輯。
【Demo】
根據現實生活中“考駕照”的需求,寫了一個小demo,規則:
1.考駕照只能按照順序來,考完一門考下一門。
2.沒門考試通過生成隨機數來判別考試是否通過,大於60分則通過,小於則繼續從該節點繼續考試。
1)uml展示:
2) 程式碼展示:
1.流程抽象類
package chainFcar; /** * 模擬處理考試流程 * 規則: * 1.考生只有依次考過了:科目一、科目二、科目三、科目四,方可拿到駕照 * 2.各次考試考過與否成績均存檔 * 3.中途未通過考試,下次從上次考試節點繼續考試 * * @author zhenhua.zhang */ public abstract class AbsExamHandler { /** * 公共方法,每個節點都會執行,子類不用Override */ protected void commonMethod(){ //todo 公共方法:eg -> //1.通過id查詢考生資訊 //2.核對考生准考證、身份證等資訊 //3.and so on 這塊可以拆分多個protected方法 } /** * 不同流程的業務起點 */ public abstract void handle(Object o, VincentChain chain); /** * 不同流程下的業務規則 * @return */ public abstract Boolean businessCheck(); protected void logBeforeExam(Integer type){ System.out.println("Vincent你的考試科目" + type + "的考試馬上開始!"); } protected void logDuringExam(Integer type,Double score){ System.out.println("Vincent你的考試科目" + type + "的成績是:" + score + "分"); } protected void logPassExam(Integer type){ if(type == 4){ System.out.println("Vincent,你通過了科目" + type + "的考核,恭喜你拿到了駕駛證!"); return; } System.out.println("Vincent,你通過了科目" + type + "的考核,接下來好好準備下一門科目" + (type+1) + "的考核吧!"); } protected void logFailExam(Integer type){ System.out.println("Vincent,你未通過了科目" + type + "的考核,請再接再厲!!!"); } }
2.科目一~四實現類
package chainFcar;
/**
* 科目一考試
* @author zhenhua.zhang
*
*/
public class ProjectOneExamHandler extends AbsExamHandler{
@Override
public void handle(Object o, VincentChain chain){
//todo
if(businessCheck()){
chain.doChain(o);
}
}
@Override
public Boolean businessCheck(){
logBeforeExam(1);
//生成1~100之間隨機數,代表考試成績,成績大於60,可以考下一門考試
Double score = (Double)(Math.random()*100);
logDuringExam(1,score);
if(score >= 60){
logPassExam(1);
return true;
}
logFailExam(1);
return false;
}
}
package chainFcar;
/**
* 科目二考試
* @author zhenhua.zhang
*
*/
public class ProjectTwoExamHandler extends AbsExamHandler{
@Override
public void handle(Object o, VincentChain chain){
//todo
if(businessCheck()){
chain.doChain(o);
}
}
@Override
public Boolean businessCheck(){
logBeforeExam(2);
//生成1~100之間隨機數,代表考試成績,成績大於60,可以考下一門考試
Double score = (Double)(Math.random()*100);
logDuringExam(2,score);
if(score >= 60){
logPassExam(2);
return true;
}
logFailExam(2);
return false;
}
}
package chainFcar;
/*
* 科目一考試
* @author zhenhua.zhang
*/
public class ProjectThreeExamHandler extends AbsExamHandler{
@Override
public void handle(Object o, VincentChain chain){
//todo
if(businessCheck()){
chain.doChain(o);
}
}
@Override
public Boolean businessCheck(){
logBeforeExam(3);
//生成1~100之間隨機數,代表考試成績,成績大於60,可以考下一門考試
Double score = (Double)(Math.random()*100);
logDuringExam(3,score);
if(score >= 60){
logPassExam(3);
return true;
}
logFailExam(3);
return false;
}
}
package chainFcar;
/**
* 科目四考試
* @author zhenhua.zhang
*
*/
public class ProjectFourExamHandler extends AbsExamHandler{
@Override
public void handle(Object o, VincentChain chain){
//todo other
if(businessCheck()){
chain.doChain(o);
}
}
@Override
public Boolean businessCheck(){
logBeforeExam(4);
Double score = (Double)(Math.random()*100);
logDuringExam(4,score);
if(score >= 60){
logPassExam(4);
return true;
}
logFailExam(4);
return false;
}
}
3.職責鏈分發、流程管理類
package chainFcar;
import java.util.ArrayList;
import java.util.List;
/**
* 職責鏈分發,流程管理
* @author zhenhua.zhang
*
*/
public class VincentChain {
//處理流程handler集合,利用List有序特性
List<AbsExamHandler> handler = new ArrayList<AbsExamHandler>();
//index標識位,用於索引handler
int index = 0;
/**
* 按順序向handler中新增元素
* @param absHandler
* @return
*/
public void addHandler(AbsExamHandler absHandler){
handler.add(absHandler);
//return this;
}
/**
* 呼叫AbsExamHandler流程
*/
public void doChain(Object o){
if(handler.size() == index){
return;
}
AbsExamHandler handlerNew = handler.get(index);
index++;
handlerNew.handle(o,this);
}
}
4.客戶端啟動訪問類
package chainFcar;
/**
*
* @author zhenhua.zhang
*
*/
public class VincentClient {
public static void main(String[] args){
VincentChain chain = new VincentChain();
ProjectOneExamHandler one = new ProjectOneExamHandler();
ProjectTwoExamHandler two = new ProjectTwoExamHandler();
ProjectThreeExamHandler three = new ProjectThreeExamHandler();
ProjectFourExamHandler four = new ProjectFourExamHandler();
chain.addHandler(one);
chain.addHandler(two);
chain.addHandler(three);
chain.addHandler(four);
Object o = new Object();
chain.doChain(o);
}
}
執行結果:
如圖,因為考科目二的時候,隨機數生成的成績只有43.72分,所以到該節點便終止了考試流程。
【小結】
一.上述例子已經展示了職責鏈模式的使用,關鍵點在於有序,這裡用到了List<AbsExamHandler>的有序性保證順序執行。
二.優缺點比較:
優點:
1.長流程保證程式碼整潔、清晰,流程節點的順序能仍能很好保證。
缺點:
1.(處理者)在實際的處理中,如果跳過了,就並沒有發揮任何的作用。那麼當這個鏈結構比較長,比較複雜的話,會產生很多的記憶體垃圾物件。這也就是職責鏈的最大缺點之所在。
2.可能不容易觀察執行時的特徵,有礙於除錯。
三、對比連結串列
1. 連結串列是一個鏈狀結構,每個節點有一個next屬性去指向他的下一節點。
2. 連結串列有一個Header節點,然後使用者每次必須通過頭節點,然後去遍歷尋找每一個節點。
可見,職責鏈相比連結串列,每次走流程,無需從頭節點開始。
That's all.