[設計模式]-責任鏈模式

1.介紹

使多個對象都有機會處理請求,從而避免請求的發送者與接受者之間的耦合關係. 將多個接受者連成一條鏈,沿着該鏈處理請求,知道請求被處理爲止.

類圖

2019-03-19-16-27-01

角色

抽象處理者: 定義了處理請求的接口或者抽象類,提供了處理請求的的方法和設置下一個處理者的方法。

具體處理者: 實現或者繼承抽象這角色,具體的實現處理邏輯.

實例與代碼

責任鏈模式有純的和不純的.

純的:當前接收者要麼處理,要麼傳遞給下一個處理者.

不純的:當前接收者可以處理一部分之後交給下一個處理者.

明顯不純的更加受歡迎嘛,比如常見的企業OA審批流程,一般是請求->直系Leader同意->總監同意,而不是直系leader直接跳過.

下面將兩種分別編碼實現以下.

純的

代碼較多,想要更清晰可以移步github.傳送門

這裏我們用打印log來模擬一下場景:
log有多種級別,debug,info,warn,error.對應着不同的處理方法
debug級別的不做處理.
info打印即可.
warn打印在標準輸出之後寫入文件保存.
error錯誤發送郵件給項目owner.

首先實現一個抽象的LogHandler,裏面定義了當前handler的級別,下一個處理者.

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public abstract class AbstractLogHandler {

  public LevelEnum levelEnum;

  private AbstractLogHandler nextHandler;

  public void setNextHandler(
      AbstractLogHandler nextHandler) {
    this.nextHandler = nextHandler;
  }

  public void handlerRequest(LogInfo info) {
    if (null == info) {
      return;
    }
    if (this.levelEnum.equals(info.levelEnum)) {
      this.consumeLog(info.content);
    } else {
      if (this.nextHandler != null) {
        this.nextHandler.handlerRequest(info);
      } else {
        return;
      }
    }
  }

  abstract void consumeLog(String content);
}

接下來是四個具體的LogHandler實現.

DEBUG:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class DebugLogHandler extends AbstractLogHandler {

  public DebugLogHandler() {
    this.levelEnum = LevelEnum.DEBUG;
  }

  @Override
  void consumeLog(String content) {
    //不做處理
    System.out.println("我是DEBUG,沒有做處理");
  }
}

INFO:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class InfoLogHandler extends AbstractLogHandler {

  public InfoLogHandler( ) {
    this.levelEnum = LevelEnum.INFO;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    System.out.println("我是INFO,直接打印完事了");

  }
}

WARN:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class WarnLogHandler extends AbstractLogHandler {

  public WarnLogHandler( ) {
    this.levelEnum = LevelEnum.WARN;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    //寫入文件
    System.out.println("我是WARN,不僅打印了還寫入文件了.");
  }
}

ERROR:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class ErrorLogHandler extends AbstractLogHandler{

  public ErrorLogHandler() {
    this.levelEnum = LevelEnum.ERROR;
  }

  @Override
  void consumeLog(String content) {
    System.out.println(content);
    System.out.println("我是ERROR,不僅打印了還寫入文件了還發了個郵件.");

  }
}

下面是兩個Model類:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class LogInfo {

  public LevelEnum levelEnum;

  public String content;

}
package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public enum LevelEnum {
  DEBUG,
  INFO,
  WARN,
  ERROR;
}

測試代碼:

package design_patterns.chain_of_responsibility_pattern.log;

/**
 * created by huyanshi on 2019/3/19
 */
public class Test {

  public static void main(String [] args ){
    //log信息
    LogInfo info = new LogInfo();
    info.content = "這是一條WARN測試log";
    info.levelEnum = LevelEnum.WARN;
    //第二條log信息
    LogInfo info1 = new LogInfo();
    info1.levelEnum = LevelEnum.ERROR;
    info1.content = "我是一條嚴重ERROR的LOG";


    //定義責任鏈
    AbstractLogHandler debugLog = new DebugLogHandler();
    AbstractLogHandler infoLog = new InfoLogHandler();
    AbstractLogHandler warnLog = new WarnLogHandler();
    AbstractLogHandler errorLog = new ErrorLogHandler();

    debugLog.setNextHandler(infoLog);
    infoLog.setNextHandler(warnLog);
    warnLog.setNextHandler(errorLog);

    debugLog.handlerRequest(info);
    debugLog.handlerRequest(info1);


  }

}

輸出如下:

這是一條WARN測試log
我是WARN,不僅打印了還寫入文件了.
我是一條嚴重ERROR的LOG
我是ERROR,不僅打印了還寫入文件了還發了個郵件.

可以看出,在這樣編碼之後,在Test類中的代碼清晰了許多.(去除掉了大量的if/else,同時,對責任鏈的初始化也可以移到別的類中,這裏不做操作.)

同時,極大的提高了擴展性,假設現在出現了第五種log級別,我們只需要重新編寫一個子類,然後再責任鏈中加入即可.

不純的

代碼較多,想要更清晰可以移步github.傳送門

其實這個場景是我學習責任鏈的初衷,那就是在一個接口內部,我們需要對傳入的多個參數(示例中防止代碼過多,使用兩個參數)進行校驗,並返回不同的error_msg,如果在方法中實現,會有大量的if/else,而且你會發現對參數的校驗代碼比真正的業務邏輯都多.嚴重影響了代碼的可讀性.

下面用簡單的代碼來實現以下:

首先是三個model類:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class BaseResponse {

  private int errCode;
  private String errMsg;

  private String content;

  public BaseResponse(BaseError error){
    this.errCode = error.errCode;
    this.errMsg = error.errMsg;
  }

  @Override
  public String toString() {
    return "errCode:" + errCode + ";" + "errMsg" + errMsg;
  }
}


package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public enum BaseError {
  SUCCESS(10000, "成功啦"),
  NAME_TOO_LONG(10001, "你的名字太長了8,不允許."),
  NAME_TOO_SHORT(10002, "你的名字不可以這麼短"),
  AGE_TOO_BIG(10003, "千年老妖怪嗎?"),
  AGE_TOO_SMALL(10004, "不支持-0.9以下的年齡哦"),
  HIGH_TOO_HIGH(10005, "我不管,姚明最高,再高不行"),
  HIGH_TOO_LOW(10006, "你個lowB,不準用.");

  int errCode;
  String errMsg;

  BaseError(int errCode, String errMsg) {
    this.errCode = errCode;
    this.errMsg = errMsg;
  }

}


package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class Person {

  public String name;

  public int age;

  public int high;

  public Person(String name, int age, int high) {
    this.name = name;
    this.age = age;
    this.high = high;
  }
}

接下來是抽象的處理類:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public abstract class AbstractParamHandler {

  private AbstractParamHandler nextHandler;

  public void setNextHandler(
      AbstractParamHandler nextHandler) {
    this.nextHandler = nextHandler;
  }

  public BaseResponse handlerRequest(Person person) {
    BaseResponse response = doCheck(person);
    if (null == response) {
      if (this.nextHandler != null) {
        return this.nextHandler.handlerRequest(person);
      } else {
        return new BaseResponse(BaseError.SUCCESS);
      }
    }
    return response;
  }

  public abstract BaseResponse doCheck(Person person);
}

需要注意的是,這個代碼和上面純的責任鏈的區別,在這份代碼中,沒有進行是否是當前"級別"的判斷,而是直接進行處理,如果當前參數校驗不通過,直接返回,當前校驗通過,繼續進行下一次校驗,如果全部通過,返回成功.

接下來是名字的處理類:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class NameHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.name == null || person.name.length() < 1) {
      return new BaseResponse(BaseError.NAME_TOO_SHORT);
    } else if (person.name.length() > 10) {
      return new BaseResponse(BaseError.NAME_TOO_LONG);
    }
    return null;
  }
}

年齡的處理類:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class AgeHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.age < -0.9) {
      return new BaseResponse(BaseError.AGE_TOO_SMALL);
    } else if (person.age > 1000) {
      return new BaseResponse(BaseError.AGE_TOO_BIG);
    }
    return null;
  }
}

身高的處理類:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class HighHandler extends AbstractParamHandler {

  @Override
  public BaseResponse doCheck(Person person) {
    if (person.high < 40) {
      return new BaseResponse(BaseError.HIGH_TOO_LOW);
    } else if (person.high > 236) {
      return new BaseResponse(BaseError.HIGH_TOO_HIGH);
    }
    return null;
  }
}

測試代碼:

package design_patterns.chain_of_responsibility_pattern.param;

/**
 * created by huyanshi on 2019/3/19
 */
public class Test {

  public static void main(String[] args) {

    AbstractParamHandler name = new NameHandler();
    AbstractParamHandler age = new AgeHandler();
    AbstractParamHandler high = new HighHandler();
    name.setNextHandler(age);
    age.setNextHandler(high);

    //成功案例
    Person person = new Person("huyanshi", 23, 172);
    System.out.println(name.handlerRequest(person));

    //名字太長案例
    Person person1 = new Person("huyanshihuyanshi",22 , 122);
    System.out.println(name.handlerRequest(person1));

    //年齡太小
    Person person2 = new Person("huyanshi",-10 , 122);
    System.out.println(name.handlerRequest(person2));
  }
}

對應的輸出如下:

errCode:10000;errMsg成功啦
errCode:10001;errMsg你的名字太長了8,不允許.
errCode:10004;errMsg不支持-0.9以下的年齡哦

模式總結

優點:

  1. 請求的發起者不知道請求會被誰處理,解耦.
  2. 請求的處理者也不知道自己會處在鏈條的哪個位置,由客戶端進行動態的裝配.
  3. 系統的擴展性強,無論是新增處理者還是刪除,甚至是打亂順序重新組合,都十分方便.

缺點:

  1. 遞歸調用,可能帶來性能問題.
  2. 遞歸調用,排查問題不方便.

思考

1. 和門面模式思想的結合

可以發現我們在Test類中的構造責任鏈的代碼很麻煩,且重複的可能性較高,比如在每個項目中可能LOG的級別只有那麼多,卻需要每次裝配一次.

我們可以再當前代碼的基礎上,提供一個四種級別LOG責任鏈的門面,當沒有發生變化時,直接調用門面的構造方法,會自動裝配這幾種LOG級別並構造成鏈,這樣可以複用代碼,且沒有破壞擴展性,當新增一個級別之後,我們可以選擇建立新的門面,也可以選擇修改舊的門面.都比較方便.

2. 和模板方法的區別與聯繫

如果看過模板方法模式,會發現責任鏈和模板方法有一點相似.

對應的關係爲:

handlerRequest方法爲父類具體方法.
doCheck爲父類抽象方法,每個子類必須自己去實現.
setNextHandler爲鉤子方法,父類提供默認實現,子類可以實現可以不實現,當設置或者不設置,會產生控制流程的作用,即爲鉤子.

是不是很像呢?

這樣結合模板方法模式的好處在哪?首先加了handlerRequest方法,把請求的傳遞判斷從子類中剝離出來,讓子類在doCheck方法中專心處理請求的業務邏輯,做到了單一職責原則。

果然是大道至簡,萬法歸一啊…

完。





ChangeLog

2019-03-19 完成

以上皆爲個人所思所得,如有錯誤歡迎評論區指正。

歡迎轉載,煩請署名並保留原文鏈接。

聯繫郵箱:[email protected]

更多學習筆記見個人博客------>呼延十

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章