責(zé)任鏈設(shè)計(jì)模式
簡(jiǎn)單介紹
View的事件分發(fā)機(jī)制是責(zé)任鏈(Chain of Responsibility)設(shè)計(jì)模式的典型應(yīng)用,其它經(jīng)典的應(yīng)用場(chǎng)景還有:JavaWeb 的過濾器、攔截器,Servlet中的請(qǐng)求響應(yīng)鏈;okhttp開源庫(kù)中也是在網(wǎng)絡(luò)層、應(yīng)用層中使用攔截器來進(jìn)行分層解耦,使的網(wǎng)絡(luò)層的配置和開發(fā)變得簡(jiǎn)單而優(yōu)雅。
案例分析
設(shè)計(jì)模式并不是一個(gè)抽象的概念,而是在編程的實(shí)踐中總結(jié)出的用于解決某類典型問題的典型范式??梢哉f,設(shè)計(jì)模式是為實(shí)戰(zhàn)而生的。這里說的責(zé)任鏈設(shè)計(jì)模式也不例外,下面看一個(gè)真實(shí)的應(yīng)用場(chǎng)景:
員工請(qǐng)假流程,普通員工三天以內(nèi)的請(qǐng)假申請(qǐng),TeamLeader直接可以直接審批通過;三到五天的請(qǐng)假申請(qǐng)由TeamLeader審批通過后技術(shù)總監(jiān)(CTO)可以審批通過;五到十天的請(qǐng)假申請(qǐng)就必須得到技術(shù)總監(jiān)(CTO)的審批通過后部門經(jīng)理(PV)可以審批通過;而十天以上的請(qǐng)假申請(qǐng)就必須得到部門經(jīng)理(PV)和總經(jīng)理(CEO)的審批通過后才能正常休假。
員工休假申請(qǐng),只需要找到自己的直接上級(jí)申請(qǐng)就可以了;直接上級(jí)可以拒絕員工的請(qǐng)假,如果請(qǐng)假時(shí)長(zhǎng)超出直接上級(jí)的審批范圍,直接上級(jí)可以向上提交該員工的請(qǐng)假申請(qǐng)。經(jīng)過具有批準(zhǔn)權(quán)限的兩級(jí)的批準(zhǔn),員工才可以正常休假。
實(shí)例流程,如下所示:
程序員提交了了角色為【程序員-小林】提交的休假請(qǐng)求;時(shí)長(zhǎng):【2】天。
項(xiàng)目組長(zhǎng)攔截了角色為【程序員-小林】提交的休假請(qǐng)求;時(shí)長(zhǎng):【2】天。
項(xiàng)目組長(zhǎng)已批準(zhǔn)角色為【程序員-小林】提交的休假請(qǐng)求;時(shí)長(zhǎng):【2】天。
程序員提交了了角色為【程序員-大鵬】提交的休假請(qǐng)求;時(shí)長(zhǎng):【3】天。
項(xiàng)目組長(zhǎng)提交了了角色為【程序員-大鵬】提交的休假請(qǐng)求;時(shí)長(zhǎng):【3】天。
技術(shù)經(jīng)理攔截了角色為【程序員-大鵬】提交的休假請(qǐng)求;時(shí)長(zhǎng):【3】天。
技術(shù)經(jīng)理已批準(zhǔn)角色為【程序員-大鵬】提交的休假請(qǐng)求;時(shí)長(zhǎng):【3】天。
程序員提交了了角色為【程序員-樊勝美】提交的休假請(qǐng)求;時(shí)長(zhǎng):【5】天。
項(xiàng)目組長(zhǎng)提交了了角色為【程序員-樊勝美】提交的休假請(qǐng)求;時(shí)長(zhǎng):【5】天。
技術(shù)經(jīng)理提交了了角色為【程序員-樊勝美】提交的休假請(qǐng)求;時(shí)長(zhǎng):【5】天。
部門經(jīng)理攔截了角色為【程序員-樊勝美】提交的休假請(qǐng)求;時(shí)長(zhǎng):【5】天。
部門經(jīng)理已批準(zhǔn)角色為【程序員-樊勝美】提交的休假請(qǐng)求;時(shí)長(zhǎng):【5】天。
程序員提交了了角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
項(xiàng)目組長(zhǎng)提交了了角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
技術(shù)經(jīng)理提交了了角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
部門經(jīng)理提交了了角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
首席執(zhí)行官攔截了角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
首席執(zhí)行官已批準(zhǔn)角色為【程序員-小蚯蚓】提交的休假請(qǐng)求;時(shí)長(zhǎng):【10】天。
項(xiàng)目組長(zhǎng)提交了了角色為【項(xiàng)目組長(zhǎng)-關(guān)關(guān)】提交的休假請(qǐng)求;時(shí)長(zhǎng):【12】天。
技術(shù)經(jīng)理提交了了角色為【項(xiàng)目組長(zhǎng)-關(guān)關(guān)】提交的休假請(qǐng)求;時(shí)長(zhǎng):【12】天。
部門經(jīng)理提交了了角色為【項(xiàng)目組長(zhǎng)-關(guān)關(guān)】提交的休假請(qǐng)求;時(shí)長(zhǎng):【12】天。
首席執(zhí)行官攔截了角色為【項(xiàng)目組長(zhǎng)-關(guān)關(guān)】提交的休假請(qǐng)求;時(shí)長(zhǎng):【12】天。
首席執(zhí)行官已批準(zhǔn)角色為【項(xiàng)目組長(zhǎng)-關(guān)關(guān)】提交的休假請(qǐng)求;時(shí)長(zhǎng):【12】天。
項(xiàng)目組長(zhǎng)提交了了角色為【項(xiàng)目組長(zhǎng)-應(yīng)勤】提交的休假請(qǐng)求;時(shí)長(zhǎng):【14】天。
技術(shù)經(jīng)理提交了了角色為【項(xiàng)目組長(zhǎng)-應(yīng)勤】提交的休假請(qǐng)求;時(shí)長(zhǎng):【14】天。
部門經(jīng)理提交了了角色為【項(xiàng)目組長(zhǎng)-應(yīng)勤】提交的休假請(qǐng)求;時(shí)長(zhǎng):【14】天。
首席執(zhí)行官攔截了角色為【項(xiàng)目組長(zhǎng)-應(yīng)勤】提交的休假請(qǐng)求;時(shí)長(zhǎng):【14】天。
首席執(zhí)行官已拒絕角色為【項(xiàng)目組長(zhǎng)-應(yīng)勤】提交的休假請(qǐng)求;時(shí)長(zhǎng):【14】天。
技術(shù)經(jīng)理提交了了角色為【技術(shù)經(jīng)理-張璇】提交的休假請(qǐng)求;時(shí)長(zhǎng):【20】天。
部門經(jīng)理提交了了角色為【技術(shù)經(jīng)理-張璇】提交的休假請(qǐng)求;時(shí)長(zhǎng):【20】天。
首席執(zhí)行官攔截了角色為【技術(shù)經(jīng)理-張璇】提交的休假請(qǐng)求;時(shí)長(zhǎng):【20】天。
首席執(zhí)行官已批準(zhǔn)角色為【技術(shù)經(jīng)理-張璇】提交的休假請(qǐng)求;時(shí)長(zhǎng):【20】天。
部門經(jīng)理提交了了角色為【部門經(jīng)理-包奕凡】提交的休假請(qǐng)求;時(shí)長(zhǎng):【60】天。
首席執(zhí)行官攔截了角色為【部門經(jīng)理-包奕凡】提交的休假請(qǐng)求;時(shí)長(zhǎng):【60】天。
首席執(zhí)行官已批準(zhǔn)角色為【部門經(jīng)理-包奕凡】提交的休假請(qǐng)求;時(shí)長(zhǎng):【60】天。
首席執(zhí)行官提交了了角色為【首席執(zhí)行官-曲筱綃】提交的休假請(qǐng)求;時(shí)長(zhǎng):【120】天。
Master攔截了角色為【首席執(zhí)行官-曲筱綃】提交的休假請(qǐng)求;時(shí)長(zhǎng):【120】天。
Master拒絕了角色為【首席執(zhí)行官-曲筱綃】提交的休假請(qǐng)求;時(shí)長(zhǎng):【120】天。
代碼設(shè)計(jì)
既然知曉了使用責(zé)任鏈解決問題,那么按照責(zé)任鏈的思想解決現(xiàn)實(shí)問題。抽象休假處理的接口,Java中接口定義規(guī)則。如下圖,IHandler接口聲明了三個(gè)方法,dispatch開頭的方法是提交請(qǐng)假申請(qǐng)的方法;onIntercept開頭的方法表示是否攔截處理這個(gè)休假申請(qǐng);handle開頭的方法表示處理這個(gè)請(qǐng)求,可以批準(zhǔn)和拒絕該申請(qǐng),自此休假申請(qǐng)不在向上級(jí)提交。
package me.ziuo.design_pattern.cop.employee;
/**
*
* @author ziyuo
* @Description 抽象出來的休假申請(qǐng)?zhí)幚斫涌? */
public interface IHandler {
/**
* 分發(fā)請(qǐng)假請(qǐng)求
* @param askModel
* @return 分發(fā)結(jié)果
*/
boolean dispatchAsk(LeaveAskModel askModel);
/**
* 攔截請(qǐng)假請(qǐng)求
* @param askModel
* @return 攔截與否
*/
boolean onInterceptAsk(LeaveAskModel askModel);
/**
* 處理請(qǐng)假請(qǐng)求
* @param askModel
* @return 是否批準(zhǔn)
*/
boolean handleAsk(LeaveAskModel askModel);
}
LeaveAskModel 休假請(qǐng)求類,該數(shù)據(jù)模型里面保存了修改的天數(shù)和申請(qǐng)人信息。如圖所示。
package me.ziuo.design_pattern.cop.employee;
/**
*
* @author ziyuo
* @Description 代表請(qǐng)假申請(qǐng)信息對(duì)象
*/
public class LeaveAskModel {
private int days;// 休假天數(shù)
private Employee askEmp;// 申請(qǐng)人
public LeaveAskModel(int days, Employee askEmp) {
super();
this.days = days;
this.askEmp = askEmp;
}
public int getDays() {
return days;
}
public void setDays(int days) {
this.days = days;
}
public Employee getAskEmp() {
return askEmp;
}
public void setAskEmp(Employee askEmp) {
this.askEmp = askEmp;
}
}
員工的抽象類實(shí)現(xiàn)IHandler里面的定義的接口的相關(guān)的方法,里面包含了員工角色信息、員工名稱信息、可處理的最大申請(qǐng)?zhí)鞌?shù)、直屬領(lǐng)導(dǎo)(當(dāng)無權(quán)限處理休假申請(qǐng)的時(shí)候可以向上提交)。
其中實(shí)現(xiàn)IHandler的方法介紹
dispatch開頭的方法的具體含義是首先判斷本角色是否攔截這個(gè)請(qǐng)求,如果攔截則調(diào)用請(qǐng)求的方法,是否審批通過取決于handleAsk的結(jié)果。
onIntercept開頭方法的具體含義,是當(dāng)角色類型是否不同于提交的對(duì)象時(shí),如果提交者申請(qǐng)的提交時(shí)長(zhǎng)可以處理的情況下,選擇攔截該請(qǐng)求后請(qǐng)求響應(yīng)的審批處理。
handle開頭的方法用于進(jìn)行審批請(qǐng)求,邏輯大概是這樣:根據(jù)員工的信息來進(jìn)行響應(yīng)的處理,這里只是一個(gè)實(shí)現(xiàn)案例。
如圖所示。
package me.ziuo.design_pattern.cop.employee;
public abstract class Employee implements IHandler{
private String actor;//員工角色名稱
private String name;//員工名稱
private int maxHandleDay;//最大可審批的請(qǐng)假天數(shù)(不包含最大天數(shù))
private Employee leader;//直屬領(lǐng)導(dǎo)
public Employee(String actor, int maxHandleDay) {
super();
this.actor = actor;
this.maxHandleDay = maxHandleDay;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public int getMaxHandleDay() {
return maxHandleDay;
}
public void setMaxHandleDay(int maxHandleDay) {
this.maxHandleDay = maxHandleDay;
}
public boolean dispatchAsk(LeaveAskModel askModel) {
if(onInterceptAsk(askModel)){
return handleAsk(askModel);
}else{
if(getLeader()!=null){
System.out.println(getActor()+"提交了了角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
return getLeader().dispatchAsk(askModel);
}else{
System.out.println(getActor()+"提交了了角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
//寫這段代碼的程序員進(jìn)行審批 ,終于站在了食物鏈的頂端(原來是在做夢(mèng))。
System.out.println("【代碼作者】"+"攔截了角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
System.out.println("【代碼作者】"+"拒絕了角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
return false;
}
}
}
public boolean onInterceptAsk(LeaveAskModel askModel) {
if(askModel!=null&&!this.getClass().equals(askModel.getAskEmp().getClass())&&askModel.getDays()<this.getMaxHandleDay()){
System.out.println(getActor()+"攔截了角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
return true;
}
return false;
}
public boolean handleAsk(LeaveAskModel askModel) {
if(askModel==null||askModel.getAskEmp().getName().equals("小帥哥0")){
System.out.println(getActor()+"已拒絕角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
return false;
}
if(askModel!=null&&askModel.getDays()<this.getMaxHandleDay()){
System.out.println(getActor()+"已批準(zhǔn)角色為【"+askModel.getAskEmp().actor+"】提交的休假請(qǐng)求;時(shí)長(zhǎng):【"+askModel.getDays()+"】天。");
return true;
}
return false;
}
public Employee getLeader() {
return leader;
}
public void setLeader(Employee leader) {
this.leader = leader;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
各個(gè)對(duì)象的封裝案例,下面列出來。
程序員類代碼
package me.ziuo.design_pattern.cop.employee;
public class Programmer extends Employee {
public Programmer() {
this("程序員", 0);
}
public Programmer(String actor, int maxHandleDay) {
super("程序員", 0);
setLeader(new TeamLeader());
}
public boolean dispatchAsk(LeaveAskModel askModel) {
return super.dispatchAsk(askModel);
}
public boolean onInterceptAsk(LeaveAskModel askModel) {
return !(askModel.getAskEmp() instanceof Programmer);
}
}
TeamLeader類代碼
package me.ziuo.design_pattern.cop.employee;
public class TeamLeader extends Employee {
public TeamLeader() {
this("項(xiàng)目組長(zhǎng)", 3);
}
public TeamLeader(String actor, int maxHandleDay) {
super("項(xiàng)目組長(zhǎng)", 3);
setLeader(new CTO());
}
}
CTO類代碼
package me.ziuo.design_pattern.cop.employee;
public class CTO extends Employee {
public CTO() {
this("技術(shù)經(jīng)理", 5);
}
public CTO(String actor, int maxHandleDay) {
super("技術(shù)經(jīng)理", 5);
setLeader(new PV());
}
}
PV類代碼
package me.ziuo.design_pattern.cop.employee;
public class PV extends Employee {
public PV(){
this("部門經(jīng)理", 10);
}
public PV(String actor, int maxHandleDay) {
super("部門經(jīng)理", 10);
setLeader(new CEO());
}
}
CEO類代碼
package me.ziuo.design_pattern.cop.employee;
public class CEO extends Employee {
public CEO(){
this("首席執(zhí)行官", 120);
}
public CEO(String actor, int maxHandleDay) {
super("首席執(zhí)行官", 120);
setLeader(null);
}
}
各個(gè)角色的向上傳遞休假申請(qǐng)的處理鏈,就組成了所謂的責(zé)任鏈;除了頂層的CEO其余的角色都持有他的直接領(lǐng)導(dǎo)。這條持有直接領(lǐng)導(dǎo)的鏈條就組成了一個(gè)單向鏈表,而責(zé)任鏈模式的部分場(chǎng)景中會(huì)直接使用單向的鏈表來鏈接責(zé)任處理集合。
責(zé)任鏈設(shè)計(jì)模到此就介紹完畢了。
如果希望了解Android事件分發(fā)流程對(duì)責(zé)任鏈設(shè)計(jì)模式的運(yùn)用請(qǐng)移步下面的鏈接。
Android事件分發(fā)流程(二)源碼解析