天天看點

JYM 設計模式系列- 責任鍊模式,裝飾模式,讓你的代碼更優雅!

作者:JAVA互聯搬磚勞工

概述設計模式分類

總體來說設計模式分為三大類:

  • 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
  • 結構型模式,共七種:擴充卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
  • 行為型模式,共十一種:政策模式、模闆方法模式、觀察者模式、疊代子模式、責任鍊模式、指令模式、備忘錄模式、狀态模式、通路者模式、中介者模式、解釋器模式。
JYM 設計模式系列- 責任鍊模式,裝飾模式,讓你的代碼更優雅!

一:責任鍊模式

1.1 名詞解釋:

責任鍊模式是一種行為設計模式,允許你将請求沿着處理者鍊進行發送。收到請求後,每個處理者均可對請求進行處理,或将其傳遞給鍊上的下個處理者。責任鍊模式的核心是解決一組服務中的先後執行處理關系。

責任鍊模式可以讓各個服務子產品更加清晰,而每一個子產品可以通過next的方式進行擷取。而每一個next是由繼承的統一抽象類實作的,最終所有類的職責可以動态的進行編排使用,編排的過程可以做成可配置化。

在使用責任鍊時,如果場景比較固定,可寫死到代碼中進行初始化。但如果業務場景經常變化可以做成xml配置的方式進行處理,也可以儲存到資料庫中進行初始化操作。

實際的業務中在使用責任鍊時會進行一系列的包裝,通過把不同的責任節點進行組裝,構成一條完整業務的責任鍊。

1.2 優點:

責任鍊模式很好的處理單一職責和開閉原則,簡單耦合也使對象關系更加清晰,而且外部的調用方并不需要關系責任鍊是如何處理的。

1.3責任鍊模式結構

  • 處理者

    聲明了所有具體處理者的通用接口,該接口通常僅包含單個方法用于請求處理,但有時其還會包含一個設定鍊上下處理者的方法。

  • 基礎處理者 是一個可選的類,你可以将所有處理者共用的樣本代碼放置在其中。(通常情況下,該類定義了一個儲存對于下個處理者引用的成員變量。用戶端可通過将處理者的構造函數或設定方法來建立鍊。該類還可以實作預設的處理行為,确定下個處理者存在後再将請求傳遞給它。)
  • 具體處理者 包含處理請求的實際代碼。每個處理者接收到請求後,都必須決定是否進行處理,或者說是否沿着鍊傳遞請求。
  • 用戶端

    可根據程式邏輯一次性或者動态的生成鍊。

1.4 适用場景

  • 當程式需要使用不同方式處理不同種類請求,而且請求類型和順序預先未知時。
  • 業務邏輯必須按順序執行多個處理者時。
  • 處理者及其順序必須在運作時進行改變,可以使用責任鍊模式。

1.5 實作方式

  • 聲明處理者接口并描述請求處理方法的簽名
  • 可以根據處理者接口建立抽象處理者基類(需要一個成員變量來存儲指向鍊上下個處理者的引用)
  • 建立具體的處理者子類并實作其處理方法。(每個處理者在接收到請求後都必須做兩個決定:1、是否自行處理請求;2、是否将該請求沿着鍊進行傳遞。)
  • 用戶端可自行組裝鍊,或者從其他對象處獲得預先組裝好的鍊。
  • 用戶端可觸發鍊中的任意處理者,而不僅僅是第一個。請求将通過鍊進行傳遞,直至某個處理者拒絕繼續傳遞或者請求到達鍊尾。

1.6 上demo

JYM 設計模式系列- 責任鍊模式,裝飾模式,讓你的代碼更優雅!

RequestHandler :請求處理器

csharp複制代碼public interface RequestHandler {

  boolean canHandleRequest(Request req);

  int getPriority();

  void handle(Request req);

  String name();
}
           

Request:

arduino複制代碼public class Request {

  /**
   * 此請求的類型,由鍊中的每個項目使用以檢視它們是否應該或可以處理
   * 這個特殊要求
   */
  private final RequestType requestType;

  /**
   * 請求的描述
   */
  private final String requestDescription;

  /**
   * 訓示請求是否已處理。請求隻能将狀态從未處理切換到
   * 已處理,無法“取消處理”請求
   */
  private boolean handled;

  /**
   * 建立給定類型和随附描述的新請求。
   *
   * @param requestType        The type of request
   * @param requestDescription The description of the request
   */
  public Request(final RequestType requestType, final String requestDescription) {
    this.requestType = Objects.requireNonNull(requestType);
    this.requestDescription = Objects.requireNonNull(requestDescription);
  }

  /**
   * 擷取請求的描述。
   *
   * @傳回請求的人可讀描述
   */
  public String getRequestDescription() {
    return requestDescription;
  }

  /**
   * G擷取此請求的類型,由指令鍊中的每個人使用以檢視他們是否應該
   * 或者可以處理這個特定的請求
   *
   * @return The request type
   */
  public RequestType getRequestType() {
    return requestType;
  }

  /**
   * 将請求标記為已處理
   */
  public void markHandled() {
    this.handled = true;
  }

  /**
   * 訓示是否處理此請求
   *
   * @return <tt>true</tt> when the request is handled, <tt>false</tt> if not
   */
  public boolean isHandled() {
    return this.handled;
  }

  @Override
  public String toString() {
    return getRequestDescription();
  }

}
           

RequestType:請求枚舉類

arduino複制代碼public enum RequestType {

  DEFEND_CASTLE, //防禦城堡
  TORTURE_PRISONER,//酷刑囚犯
  COLLECT_TAX //收稅

}
           

OrcCommander:獸人指揮官

typescript複制代碼@Slf4j
public class OrcCommander implements RequestHandler {
  @Override
  public boolean canHandleRequest(Request req) {
    return req.getRequestType() == RequestType.DEFEND_CASTLE;
  }

  @Override
  public int getPriority() {
    return 2;
  }

  @Override
  public void handle(Request req) {
    req.markHandled();
    LOGGER.info("{} handling request "{}"", name(), req);
  }

  @Override
  public String name() {
    return "Orc commander";
  }
}
           

OrcKing: 發出由鍊處理的請求

scss複制代碼public class OrcKing {

  private List<RequestHandler> handlers;

  public OrcKing() {
    buildChain();
  }

  private void buildChain() {
    handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier());
  }

  /**
   * Handle request by the chain.
   */
  public void makeRequest(Request req) {
    handlers
        .stream()
        .sorted(Comparator.comparing(RequestHandler::getPriority))
        .filter(handler -> handler.canHandleRequest(req))
        .findFirst()
        .ifPresent(handler -> handler.handle(req));
  }
}
           

OrcOfficer:獸人軍官

typescript複制代碼@Slf4j
public class OrcOfficer implements RequestHandler {
  @Override
  public boolean canHandleRequest(Request req) {
    return req.getRequestType() == RequestType.TORTURE_PRISONER;
  }

  @Override
  public int getPriority() {
    return 3;
  }

  @Override
  public void handle(Request req) {
    req.markHandled();
    LOGGER.info("{} handling request "{}"", name(), req);
  }

  @Override
  public String name() {
    return "Orc officer";
  }
}
           

OrcSoldier:獸人士兵

typescript複制代碼@Slf4j
public class OrcSoldier implements RequestHandler {
  @Override
  public boolean canHandleRequest(Request req) {
    return req.getRequestType() == RequestType.COLLECT_TAX;
  }

  @Override
  public int getPriority() {
    return 1;
  }

  @Override
  public void handle(Request req) {
    req.markHandled();
    LOGGER.info("{} handling request "{}"", name(), req);
  }

  @Override
  public String name() {
    return "Orc soldier";
  }
}
           

程式入口:

typescript複制代碼public class App {

  /**
   * Program entry point.
   *
   * @param args command line args
   */
  public static void main(String[] args) {

    var king = new OrcKing();
    king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle 保衛城堡"));
    king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner 酷刑囚犯"));
    king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax 征稅"));
  }
}
           

在這個例子中,我們将請求處理程式 RequestHandler組織成一個鍊,其中 每個處理程式都有機會輪流處理請求。這裡國王 OrcKing送出請求,軍事獸人 OrcCommander, OrcOfficer, OrcSoldier 形成處理程式鍊。

二:裝飾模式

2.1 什麼是裝飾模式解釋:

裝飾模式又名包裝模式(Wrapper)。裝飾模式是以對用戶端透明的方式擴充對象的功能,是繼承關系的一種替代方案。

  • 裝飾模式以對客戶透明的方式動态的給對象附加更多的責任,換言之客戶并不會覺得對象在裝飾前和裝飾後有什麼差別。
  • 裝飾模式可以在不增加子類的情況下,将對象的功能擴充。
  • 裝飾模式把用戶端的調用委派到被裝飾類,裝飾模式的關鍵在于這種功能的擴充是透明的。
  • 裝飾模式是在不必改變原類檔案和使用繼承的情況下,動态的擴充一個對象的功能,它是通過建立一個包裝對象,也就是裝飾來包裹真是的對象。

2.2 裝飾模式的組成

  • 抽象構件角色(Component):給出一個抽象接口,已規範準備接受附加責任的對象。
  • 具體構件角色(Concrete Component):定義一個将要接收附加責任的類。
  • 裝飾角色(Decrator):持有一個建構角色對象的引用,并定義一個與抽象建構角色一緻的接口。
  • 具體裝飾角色(Concrete Decrator):負責給建構對象貼上附加的責任。

2.3 裝飾模式的優點

裝飾模式的優點:

  • 裝飾模式與繼承關系的目的都是要擴充對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
  • 可以通過一種動态的方式來擴充一個對象的功能,通過配置檔案可以在運作時選擇不同的裝飾器,進而實作不同的行為。
  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象。
  • 具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”

裝飾模式的缺點:

  • 使用裝飾模式進行系統設計時将産生很多小對象,這些對象的差別在于它們之間互相連接配接的方式有所不同,而不是它們的類或者屬性值有所不同,同時還将産生很多具體裝飾類。這些裝飾類和小對象的産生将增加系統的複雜度,加大學習與了解的難度。
  • 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為煩瑣。

2.4裝飾模式的适用環境

在不影響其他對象的情況下,以動态、透明的方式給單個對象添加職責。 需要動态地給一個對象增加功能,這些功能也可以動态地被撤銷。 當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴充和維護時。不能采用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充,為支援每一種組合将産生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類).

2.5 上demo

JYM 設計模式系列- 責任鍊模式,裝飾模式,讓你的代碼更優雅!

Troll:巨魔接口

csharp複制代碼public interface Troll {

  void attack(); //攻擊

  int getAttackPower(); // 擷取攻擊力

  void fleeBattle(); //逃離戰鬥

}
           

棒子巨魔實作巨魔接口:

typescript複制代碼@Slf4j
@RequiredArgsConstructor
public class ClubbedTroll implements Troll {

  private final Troll decorated;

  @Override
  public void attack() {
    decorated.attack();
    LOGGER.info("The troll swings at you with a club!");
  }

  @Override
  public int getAttackPower() {
    return decorated.getAttackPower() + 10;
  }

  @Override
  public void fleeBattle() {
    decorated.fleeBattle();
  }
}
           

一般的巨魔:

typescript複制代碼@Slf4j
public class SimpleTroll implements Troll {

  @Override
  public void attack() {
    LOGGER.info("The troll tries to grab you!");
  }

  @Override
  public int getAttackPower() {
    return 10;
  }

  @Override
  public void fleeBattle() {
    LOGGER.info("The troll shrieks in horror and runs away!");
  }
}
           

程式入口:

scss複制代碼@Slf4j
public class App {

  /**
   * Program entry point.
   *
   * @param args command line args
   */
  public static void main(String[] args) {

    // simple troll
    LOGGER.info("A simple looking troll approaches.");
    var troll = new SimpleTroll();
    troll.attack();
    troll.fleeBattle();
    LOGGER.info("Simple troll power: {}.\n", troll.getAttackPower());

    // 通過添加裝飾器改變簡單巨魔的行為
    LOGGER.info("A troll with huge club surprises you.");
    var clubbedTroll = new ClubbedTroll(troll);
    clubbedTroll.attack();
    clubbedTroll.fleeBattle();
    LOGGER.info("Clubbed troll power: {}.\n", clubbedTroll.getAttackPower());
  }
}
           

在這個例子中,我們展示了簡單的 SimpleTroll 如何首先攻擊然後逃離 戰鬥。然後我們用 ClubbedTroll裝飾 SimpleTroll 并再次執行攻擊。可以看到裝飾後行為發生了怎樣的變化。

裝飾器模式是子類化的更靈活的替代方案。 Decorator 類實作與目标相同的接口,并使用組合來“裝飾”對目标的調用。使用裝飾器模式可以在運作時改變類的行為。

作者:小螞蟻技術

連結:https://juejin.cn/post/7237540467427590199

繼續閱讀