JYM 设计模式系列- 责任链模式,装饰模式,让你的代码更优雅!!!

7,062 阅读10分钟

觉得不错请按下图操作,掘友们,哈哈哈!!! image.png

概述设计模式分类

总体来说设计模式分为三大类:

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

一:责任链模式

1.1 名词解释:

责任链模式是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。责任链模式的核心是解决一组服务中的先后执行处理关系。

责任链模式可以让各个服务模块更加清晰,而每一个模块可以通过next的方式进行获取。而每一个next是由继承的统一抽象类实现的,最终所有类的职责可以动态的进行编排使用,编排的过程可以做成可配置化。

在使用责任链时,如果场景比较固定,可写死到代码中进行初始化。但如果业务场景经常变化可以做成xml配置的方式进行处理,也可以保存到数据库中进行初始化操作。

实际的业务中在使用责任链时会进行一系列的包装,通过把不同的责任节点进行组装,构成一条完整业务的责任链。

1.2 优点:

责任链模式很好的处理单一职责和开闭原则,简单耦合也使对象关系更加清晰,而且外部的调用方并不需要关系责任链是如何处理的。

1.3责任链模式结构

  • 处理者
    声明了所有具体处理者的通用接口,该接口通常仅包含单个方法用于请求处理,但有时其还会包含一个设置链上下处理者的方法。
  • 基础处理者 是一个可选的类,你可以将所有处理者共用的样本代码放置在其中。(通常情况下,该类定义了一个保存对于下个处理者引用的成员变量。客户端可通过将处理者的构造函数或设定方法来创建链。该类还可以实现默认的处理行为,确定下个处理者存在后再将请求传递给它。)
  • 具体处理者 包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,或者说是否沿着链传递请求。
  • 客户端
    可根据程序逻辑一次性或者动态的生成链。

1.4 适用场景

  • 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时。
  • 业务逻辑必须按顺序执行多个处理者时。
  • 处理者及其顺序必须在运行时进行改变,可以使用责任链模式。

1.5 实现方式

  • 声明处理者接口并描述请求处理方法的签名
  • 可以根据处理者接口创建抽象处理者基类(需要一个成员变量来存储指向链上下个处理者的引用)
  • 创建具体的处理者子类并实现其处理方法。(每个处理者在接收到请求后都必须做两个决定:1、是否自行处理请求;2、是否将该请求沿着链进行传递。)
  • 客户端可自行组装链,或者从其他对象处获得预先组装好的链。
  • 客户端可触发链中的任意处理者,而不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递或者请求到达链尾。

1.6 上demo

image.png

RequestHandler :请求处理器

public interface RequestHandler {

  boolean canHandleRequest(Request req);

  int getPriority();

  void handle(Request req);

  String name();
}

Request:

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:请求枚举类

public enum RequestType {

  DEFEND_CASTLE, //防御城堡
  TORTURE_PRISONER,//酷刑囚犯
  COLLECT_TAX //收税

}

OrcCommander:兽人指挥官

@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: 发出由链处理的请求

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:兽人军官

@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:兽人士兵

@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";
  }
}

程序入口:

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

image.png

Troll:巨魔接口

public interface Troll {

  void attack(); //攻击

  int getAttackPower(); // 获取攻击力

  void fleeBattle(); //逃离战斗

}

棒子巨魔实现巨魔接口:

@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();
  }
}

一般的巨魔:

@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!");
  }
}

程序入口:

@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 类实现与目标相同的接口,并使用组合来“装饰”对目标的调用。使用装饰器模式可以在运行时改变类的行为。