同事寫了一個(gè)責(zé)任鏈模式,bug無數(shù)!
什么是責(zé)任鏈 場景
反例 初步改造 缺點(diǎn) 責(zé)任鏈改造 責(zé)任鏈工廠改造 聊聊其他 最近,我讓團(tuán)隊(duì)內(nèi)一位成員寫了一個(gè)導(dǎo)入功能。他使用了責(zé)任鏈模式,代碼堆的非常多,bug 也多,沒有達(dá)到我預(yù)期的效果。實(shí)際上,針對導(dǎo)入功能,我認(rèn)為模版方法更合適!為此,隔壁團(tuán)隊(duì)也拿出我們的案例,進(jìn)行了集體 code review。
學(xué)好設(shè)計(jì)模式,且不要為了練習(xí),強(qiáng)行使用!讓原本100行就能實(shí)現(xiàn)的功能,寫了 3000 行!
對錯(cuò)暫且不論,我們先一起看看責(zé)任鏈設(shè)計(jì)模式吧!
什么是責(zé)任鏈
「責(zé)任鏈模式」 是一種行為設(shè)計(jì)模式, 允許你將請求沿著處理者鏈進(jìn)行發(fā)送。收到請求后, 每個(gè)處理者均可對請求進(jìn)行處理, 或?qū)⑵鋫鬟f給鏈上的下個(gè)處理者。
場景
責(zé)任鏈的使用場景還是比較多的
多條件流程判斷:權(quán)限控制 ERP 系統(tǒng)流程審批:總經(jīng)理、人事經(jīng)理、項(xiàng)目經(jīng)理 Java 過濾器 的底層實(shí)現(xiàn) Filter 如果不使用該設(shè)計(jì)模式,那么當(dāng)需求有所改變時(shí),就會(huì)使得代碼臃腫或者難以維護(hù),例如下面的例子
反例
假設(shè)現(xiàn)在有一個(gè)闖關(guān)游戲,進(jìn)入下一關(guān)的條件是上一關(guān)的分?jǐn)?shù)要高于xx
游戲一共 3 個(gè)關(guān)卡 進(jìn)入第二關(guān)需要第一關(guān)的游戲得分大于等于 80 進(jìn)入第三關(guān)需要第二關(guān)的游戲得分大于等于 90 那么代碼可以這樣寫
//第一關(guān)
public class FirstPassHandler {
public int handler(){
System.out.println("第一關(guān)-->FirstPassHandler");
return 80;
}
}
//第二關(guān)
public class SecondPassHandler {
public int handler(){
System.out.println("第二關(guān)-->SecondPassHandler");
return 90;
}
}
//第三關(guān)
public class ThirdPassHandler {
public int handler(){
System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");
return 95;
}
}
//客戶端
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)
int firstScore = firstPassHandler.handler();
//第一關(guān)的分?jǐn)?shù)大于等于80則進(jìn)入第二關(guān)
if(firstScore >= 80){
int secondScore = secondPassHandler.handler();
//第二關(guān)的分?jǐn)?shù)大于等于90則進(jìn)入第二關(guān)
if(secondScore >= 90){
thirdPassHandler.handler();
}
}
}
}那么如果這個(gè)游戲有100關(guān),我們的代碼很可能就會(huì)寫成這個(gè)樣子
if(第1關(guān)通過){
// 第2關(guān) 游戲
if(第2關(guān)通過){
// 第3關(guān) 游戲
if(第3關(guān)通過){
// 第4關(guān) 游戲
if(第4關(guān)通過){
// 第5關(guān) 游戲
if(第5關(guān)通過){
// 第6關(guān) 游戲
if(第6關(guān)通過){
//...
}
}
}
}
}
}這種代碼不僅冗余,并且當(dāng)我們要將某兩關(guān)進(jìn)行調(diào)整時(shí)會(huì)對代碼非常大的改動(dòng),這種操作的風(fēng)險(xiǎn)是很高的,因此,該寫法非常糟糕
初步改造
如何解決這個(gè)問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guān) ....,這樣客戶端就不需要進(jìn)行多重 if 的判斷了
public class FirstPassHandler {
/**
* 第一關(guān)的下一關(guān)是 第二關(guān)
*/
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
this.secondPassHandler = secondPassHandler;
}
//本關(guān)卡游戲得分
private int play(){
return 80;
}
public int handler(){
System.out.println("第一關(guān)-->FirstPassHandler");
if(play() >= 80){
//分?jǐn)?shù)>=80 并且存在下一關(guān)才進(jìn)入下一關(guān)
if(this.secondPassHandler != null){
return this.secondPassHandler.handler();
}
}
return 80;
}
}
public class SecondPassHandler {
/**
* 第二關(guān)的下一關(guān)是 第三關(guān)
*/
private ThirdPassHandler thirdPassHandler;
public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
this.thirdPassHandler = thirdPassHandler;
}
//本關(guān)卡游戲得分
private int play(){
return 90;
}
public int handler(){
System.out.println("第二關(guān)-->SecondPassHandler");
if(play() >= 90){
//分?jǐn)?shù)>=90 并且存在下一關(guān)才進(jìn)入下一關(guān)
if(this.thirdPassHandler != null){
return this.thirdPassHandler.handler();
}
}
return 90;
}
}
public class ThirdPassHandler {
//本關(guān)卡游戲得分
private int play(){
return 95;
}
/**
* 這是最后一關(guān),因此沒有下一關(guān)
*/
public int handler(){
System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");
return play();
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)
firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)
secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)
//說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)
//開始調(diào)用第一關(guān) 每一個(gè)關(guān)卡是否進(jìn)入下一關(guān)卡 在每個(gè)關(guān)卡中判斷
firstPassHandler.handler();
}
}缺點(diǎn)
現(xiàn)有模式的缺點(diǎn)
每個(gè)關(guān)卡中都有下一關(guān)的 成員變量并且是不一樣的,形成鏈很不方便代碼的擴(kuò)展性非常不好 責(zé)任鏈改造
既然每個(gè)關(guān)卡中都有下一關(guān)的 成員變量并且是不一樣的,那么我們可以在關(guān)卡上抽象出一個(gè)父類或者接口,然后每個(gè)具體的關(guān)卡去繼承或者實(shí)現(xiàn)有了思路,我們先來簡單介紹一下責(zé)任鏈設(shè)計(jì)模式的「基本組成」
抽象處理者(Handler)角色:定義一個(gè)處理請求的接口,包含抽象處理方法和一個(gè)后繼連接。 具體處理者(Concrete Handler)角色:實(shí)現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者。 客戶類(Client)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細(xì)節(jié)和請求的傳遞過程。
責(zé)任鏈改造 public abstract class AbstractHandler {
/**
* 下一關(guān)用當(dāng)前抽象類來接收
*/
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract int handler();
}
public class FirstPassHandler extends AbstractHandler{
private int play(){
return 80;
}
@Override
public int handler(){
System.out.println("第一關(guān)-->FirstPassHandler");
int score = play();
if(score >= 80){
//分?jǐn)?shù)>=80 并且存在下一關(guān)才進(jìn)入下一關(guān)
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class SecondPassHandler extends AbstractHandler{
private int play(){
return 90;
}
public int handler(){
System.out.println("第二關(guān)-->SecondPassHandler");
int score = play();
if(score >= 90){
//分?jǐn)?shù)>=90 并且存在下一關(guān)才進(jìn)入下一關(guān)
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class ThirdPassHandler extends AbstractHandler{
private int play(){
return 95;
}
public int handler(){
System.out.println("第三關(guān)-->ThirdPassHandler");
int score = play();
if(score >= 95){
//分?jǐn)?shù)>=95 并且存在下一關(guān)才進(jìn)入下一關(guān)
if(this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler();//第一關(guān)
SecondPassHandler secondPassHandler = new SecondPassHandler();//第二關(guān)
ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三關(guān)
// 和上面沒有更改的客戶端代碼相比,只有這里的set方法發(fā)生變化,其他都是一樣的
firstPassHandler.setNext(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)
secondPassHandler.setNext(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)
//說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)
//從第一個(gè)關(guān)卡開始
firstPassHandler.handler();
}
}責(zé)任鏈工廠改造
對于上面的請求鏈,我們也可以把這個(gè)關(guān)系維護(hù)到配置文件中或者一個(gè)枚舉中。我將使用枚舉來教會(huì)大家怎么動(dòng)態(tài)的配置請求鏈并且將每個(gè)請求者形成一條調(diào)用鏈。
public enum GatewayEnum {
// handlerId, 攔截者名稱,全限定類名,preHandlerId,nextHandlerId
API_HANDLER(new GatewayEntity(1, "api接口限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
BLACKLIST_HANDLER(new GatewayEntity(2, "黑名單攔截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
SESSION_HANDLER(new GatewayEntity(3, "用戶會(huì)話攔截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null)),
;
GatewayEntity gatewayEntity;
public GatewayEntity getGatewayEntity() {
return gatewayEntity;
}
GatewayEnum(GatewayEntity gatewayEntity) {
this.gatewayEntity = gatewayEntity;
}
}
public class GatewayEntity {
private String name;
private String conference;
private Integer handlerId;
private Integer preHandlerId;
private Integer nextHandlerId;
}
public interface GatewayDao {
/**
* 根據(jù) handlerId 獲取配置項(xiàng)
* @param handlerId
* @return
*/
GatewayEntity getGatewayEntity(Integer handlerId);
/**
* 獲取第一個(gè)處理者
* @return
*/
GatewayEntity getFirstGatewayEntity();
}
public class GatewayImpl implements GatewayDao {
/**
* 初始化,將枚舉中配置的handler初始化到map中,方便獲取
*/
private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
static {
GatewayEnum[] values = GatewayEnum.values();
for (GatewayEnum value : values) {
GatewayEntity gatewayEntity = value.getGatewayEntity();
gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
}
}
@Override
public GatewayEntity getGatewayEntity(Integer handlerId) {
return gatewayEntityMap.get(handlerId);
}
@Override
public GatewayEntity getFirstGatewayEntity() {
for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
GatewayEntity value = entry.getValue();
// 沒有上一個(gè)handler的就是第一個(gè)
if (value.getPreHandlerId() == null) {
return value;
}
}
return null;
}
}
public class GatewayHandlerEnumFactory {
private static GatewayDao gatewayDao = new GatewayImpl();
// 提供靜態(tài)方法,獲取第一個(gè)handler
public static GatewayHandler getFirstGatewayHandler() {
GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
if (firstGatewayHandler == null) {
return null;
}
GatewayEntity tempGatewayEntity = firstGatewayEntity;
Integer nextHandlerId = null;
GatewayHandler tempGatewayHandler = firstGatewayHandler;
// 迭代遍歷所有handler,以及將它們鏈接起來
while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
tempGatewayHandler.setNext(gatewayHandler);
tempGatewayHandler = gatewayHandler;
tempGatewayEntity = gatewayEntity;
}
// 返回第一個(gè)handler
return firstGatewayHandler;
}
/**
* 反射實(shí)體化具體的處理者
* @param firstGatewayEntity
* @return
*/
private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
// 獲取全限定類名
String className = firstGatewayEntity.getConference();
try {
// 根據(jù)全限定類名,加載并初始化該類,即會(huì)初始化該類的靜態(tài)段
Class<?> clazz = Class.forName(className);
return (GatewayHandler) clazz.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}聊聊其他
設(shè)計(jì)模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。
設(shè)計(jì)模式確實(shí)是一門藝術(shù),仍需努力呀!
· END ·
熱門推薦:
PS:如果覺得我的分享不錯(cuò),歡迎大家隨手點(diǎn)贊、轉(zhuǎn)發(fā)、在看。



