java設(shè)計模式-責任鏈模式

今天給大家介紹責任鏈模式(Chain of Responsibility Pattern),責任鏈模式是屬于設(shè)計模式中的行為型模式。巧用設(shè)計模式可以讓我們的代碼解耦,提高可拓展性和維護性,但一定不要為了使用而使用。

一、定義

  • 原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

使多個對象都有機會處理請求,從而避免了請求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。

  • 維基百科定義:In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.

在面向?qū)ο蟮脑O(shè)計中,責任鏈模式是一種由命令對象的源和一系列處理對象組成的設(shè)計模式。每個處理對象都包含定義其可以處理的命令對象類型的邏輯。其余的將傳遞到鏈中的下一個處理對象。

  • 簡要總結(jié):責任鏈模式是將鏈中的每一個節(jié)點看做一個對象,且內(nèi)部維護下一個節(jié)點對象的引用,形成一個單鏈表,當收到一個請求時,會沿著鏈的路徑一次傳遞給每一個節(jié)點對象,當然每個節(jié)點對象可以選擇繼續(xù)傳遞或者提前退出,直到最后一個節(jié)點對象。

二、應(yīng)用

1. 應(yīng)用場景

責任鏈模式的使用也是比較廣泛的,比如我們OA系統(tǒng)的審批流,或者某一個特殊的業(yè)務(wù)需要經(jīng)過幾個比較復雜的流程處理等,比如一個添加業(yè)務(wù),假設(shè)我們先要進行數(shù)據(jù)校驗,然后進行入庫操作,最后要通知其他系統(tǒng)做特殊處理,就可以做成一個責任鏈模式,代碼整體耦合性降低,易于維護。

2. 角色
  • Handler(抽象處理者)

    定義請求處理方法,以及維護下一個處理者的引用

  • ConcreteHandler(具體處理者)

    處理請求的具體角色

3. 類圖

責任鏈模式的類圖比較簡單,所有處理節(jié)點都繼承同一個父類,這個父類可以是抽象類或者具體實現(xiàn)類,類中維護下一個處理節(jié)點的引用以及節(jié)點的處理請求方法(這個方法可以是抽象方法,也可以是具體實現(xiàn),當然還可以利用鉤子方法實現(xiàn)對子類方法的調(diào)用),調(diào)用者不關(guān)注具體節(jié)點的實現(xiàn),只調(diào)用節(jié)點的處理方法即可。

image
4. 舉例說明

假設(shè)現(xiàn)在有一個請假流程,小于三天組長審批即可,大于3天同時需要CTO審批,大于10天同時需要CEO審批。組長和CTO以及CEO都有權(quán)利審批通過或者駁回,如果審批通過,根據(jù)請假天數(shù)判斷是否需要流轉(zhuǎn)到下一節(jié)點,如果駁回請求,則直接返回。

首先看下整體類圖:

image

首先創(chuàng)建一個父類處理器,

public class RequestHandler {

  private RequestHandler next; // 下一個節(jié)點的引用

  public RequestHandler(RequestHandler next) {
    this.next = next;
  }

  public void handleRequest(Request request) {
    if (next != null) { // 如果下一個節(jié)點不為空,則繼續(xù)調(diào)用下一個節(jié)點的處理方法
      next.handleRequest(request);
    }
  }

}

創(chuàng)建三個節(jié)點處理對象,分別是組長處理、CTO處理、CEO處理:

public class LeaderHandler extends RequestHandler {

  public LeaderHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
    if (request.getEmployeeName().equals("張三")) {
      System.out.println("組長駁回【 " + request.getEmployeeName() + " 】請假");
      request.setReject();
      return;
    }
    System.out.println("組長通過【 " + request.getEmployeeName() + " 】請假");
    if (request.getDays() > 3) {
      super.handleRequest(request);
    } else {
      request.setPass();
    }
  }

}
public class CTOHandler extends RequestHandler {

  public CTOHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
   if (request.getEmployeeName().equals("李四")) {
      System.out.println("CTO駁回【 " + request.getEmployeeName() + " 】請假");
      request.setReject();
      return;
    }
    System.out.println("CTO通過【 " + request.getEmployeeName() + " 】請假");
    if (request.getDays() > 10) {
      super.handleRequest(request);
    } else {
      request.setPass();
    }

  }

}
public class CEOHandler extends RequestHandler {

  public CEOHandler(RequestHandler next) {
    super(next);
  }

  @Override
  public void handleRequest(Request request) {
    if (request.getEmployeeName().equals("王五")) {
      System.out.println("CEO駁回【 " + request.getEmployeeName() + " 】請假");
      request.setReject();
      return;
    }
    System.out.println("CEO通過【 " + request.getEmployeeName() + " 】請假");
    request.setPass();
  }

}

然后創(chuàng)建一個請假處理對象:

public class Request {

  private String employeeName; // 員工姓名

  private int days; // 請假天數(shù)

  private boolean handleResult; // 請假是否通過

  public Request(String employeeName, int days) {
    this.employeeName = employeeName;
    this.days = days;
  }

  public String getEmployeeName() {
    return employeeName;
  }

  public void setEmployeeName(String employeeName) {
    this.employeeName = employeeName;
  }

  public int getDays() {
    return days;
  }

  public void setDays(int days) {
    this.days = days;
  }

  public void setPass() {
    this.handleResult = true;
  }

  public void setReject() {
    this.handleResult = false;
  }

  public boolean getHandleResult() {
    return this.handleResult;
  }
}

建立一個測試類:

public class ChainTest {

  public static void main(String[] args) {
    RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));

    Request zhangsan = new Request("張三", 3);
    chain.handleRequest(zhangsan);
    System.out.println("張三請假結(jié)果:" + zhangsan.getHandleResult());
    System.out.println("-------------------");

    Request lisi = new Request("李四", 5);
    chain.handleRequest(lisi);
    System.out.println("李四請假結(jié)果:" + lisi.getHandleResult());
    System.out.println("-------------------");

    Request wangwu = new Request("王五", 15);
    chain.handleRequest(wangwu);
    System.out.println("王五請假結(jié)果:" + wangwu.getHandleResult());
    System.out.println("-------------------");

    Request oldZhao = new Request("老趙", 15);
    chain.handleRequest(oldZhao);
    System.out.println("老趙請假結(jié)果:" + oldZhao.getHandleResult());
  }

}

打印結(jié)果如下:

組長駁回【 張三 】請假
張三請假結(jié)果:false
-------------------
組長通過【 李四 】請假
CTO駁回【 李四 】請假
李四請假結(jié)果:false
-------------------
組長通過【 王五 】請假
CTO通過【 王五 】請假
CEO駁回【 王五 】請假
王五請假結(jié)果:false
-------------------
組長通過【 老趙 】請假
CTO通過【 老趙 】請假
CEO通過【 老趙 】請假
老趙請假結(jié)果:true

我們可以看到輸入不同的參數(shù),所走的業(yè)務(wù)流程不同,有的只經(jīng)過一個處理器,有的會走完所有的處理器。

三、和建造者模式的結(jié)合使用

上面的示例中,在父類維護next節(jié)點的引用我們是通過構(gòu)造器來維護的,所以我們在初始化處理鏈的時候是通過下面代碼實現(xiàn)的

RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));

可以看到代碼整體顯得比較臃腫,如果我們有六七個或者更多處理節(jié)點的時候,易讀性會非常差,
而大多數(shù)情況下一般都是通過父類提供一個setNextHandler(RequestHandler handler)來維護下一個節(jié)點,如果用此方法來初始化處理鏈的話,代碼如下:

LeaderHandler leader = new LeaderHandler();
CTOHandler cto = new CTOHandler();
CEOHandler ceo = new CEOHandler();
leader.setNextHandler(cto);
cto.setNextHandler(ceo);

我們可以看到需要不斷的給當前節(jié)點設(shè)置下一個節(jié)點,易讀性和維護性也是比較差的,至此我們想到了利用建造者模式和責任鏈模式的結(jié)合使用,來讓此處的設(shè)計變的解耦又好維護。

我們在RequestHandler類中維護一個內(nèi)部類,內(nèi)部類中使用單向鏈表來維護處理節(jié)點的順序,再通過addHandler方法添加所有處理鏈,在調(diào)用的時候提供一個build方法獲取頭結(jié)點,然后通過頭節(jié)點的next屬性獲取后續(xù)所有的節(jié)點,這樣只要我們在初始化處理鏈的時候按順序添加處理結(jié)點,就不用在設(shè)置next結(jié)點,代碼的可讀性也提升了很多。

public class RequestHandler {

  private RequestHandler next;

  public void setNextHandler(RequestHandler next) {
    this.next = next;
  }

  public void handleRequest(Request request) {
    if (next != null) {
      next.handleRequest(request);
    }
  }

  public static class Builder {

    private RequestHandler head;

    private RequestHandler tail;

    public Builder addHandler(RequestHandler requestHandler) {
      if (head == null) {
        this.head = this.tail = requestHandler;
      } else {
        this.tail.next = requestHandler;
        this.tail = requestHandler;
      }
      return this;
    }

    public RequestHandler build() {
      return this.head;
    }

  }

}

調(diào)用的時候通過如下代碼即可:

public class ChainTest {

  public static void main(String[] args) {

    RequestHandler.Builder builder = new RequestHandler.Builder();
    builder.addHandler(new LeaderHandler())
        .addHandler(new CTOHandler())
        .addHandler(new CEOHandler());
    RequestHandler handler = builder.build();

    Request zhangsan = new Request("張三", 3);
    handler.handleRequest(zhangsan);
    System.out.println("張三請假結(jié)果:" + zhangsan.getHandleResult());
    System.out.println("-------------------");

    Request lisi = new Request("李四", 5);
    handler.handleRequest(lisi);
    System.out.println("李四請假結(jié)果:" + lisi.getHandleResult());
    System.out.println("-------------------");

    Request wangwu = new Request("王五", 15);
    handler.handleRequest(wangwu);
    System.out.println("王五請假結(jié)果:" + wangwu.getHandleResult());
    System.out.println("-------------------");

    Request oldZhao = new Request("老趙", 15);
    handler.handleRequest(oldZhao);
    System.out.println("老趙請假結(jié)果:" + oldZhao.getHandleResult());
  }

}

四、在源碼中的體現(xiàn)

public void log(LogRecord record) {
  if (!isLoggable(record.getLevel())) {
    return;
  }
  Filter theFilter = config.filter;
  if (theFilter != null && !theFilter.isLoggable(record)) {
    return;
  }

  // Post the LogRecord to all our Handlers, and then to
  // our parents' handlers, all the way up the tree.

  Logger logger = this;
  while (logger != null) {
    final Handler[] loggerHandlers = isSystemLogger
      ? logger.accessCheckedHandlers()
      : logger.getHandlers();

    for (Handler handler : loggerHandlers) {
      handler.publish(record);
    }

    final boolean useParentHdls = isSystemLogger
      ? logger.config.useParentHandlers
      : logger.getUseParentHandlers();

    if (!useParentHdls) {
      break;
    }

    logger = isSystemLogger ? logger.parent : logger.getParent();
  }
}

此處是把所有的handler都維護在一個數(shù)組中,然后通過循環(huán)handler調(diào)用next的處理結(jié)點

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
  Assert.notNull(request, "Request must not be null");
  Assert.notNull(response, "Response must not be null");
  Assert.state(this.request == null, "This FilterChain has already been called!");
  if (this.iterator == null) {
    this.iterator = this.filters.iterator();
  }

  if (this.iterator.hasNext()) {
    Filter nextFilter = (Filter)this.iterator.next();
    nextFilter.doFilter(request, response, this);
  }

  this.request = request;
  this.response = response;
}

這里是將所有的Filter放到list中,然后通過調(diào)用doFilter()方法時循環(huán)迭代list,list中的Filter也會順序執(zhí)行。

五、優(yōu)缺點

優(yōu)點:
  • 降低代碼的耦合度,將請求與處理解耦
  • 節(jié)點對象只需關(guān)注自己要處理的業(yè)務(wù)或者特定的請求即可,對于不想處理的可以直接傳遞給下一個節(jié)點
  • 鏈路結(jié)構(gòu)靈活,可以通過改變鏈路結(jié)構(gòu)動態(tài)的新增或者刪減責任
  • 可以實時新增鏈路節(jié)點,符合開閉原則
缺點:
  • 責任鏈太長或者處理時間過長,會影響整體性能
  • 整體調(diào)用有點類似遞歸,排查問題較為費力
  • 代碼拆分的小類會比較多
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容