是你,还是你,所有都有你!—— 装璜者模式

一、概述

装璜者模式(Decorator Pattern)容许向一个现有的对象扩大新的性能,同时不扭转其构造。次要解决间接继承下因性能的一直横向扩大导致子类收缩的问题,无需思考子类的保护。

装璜者模式有4种角色:

  1. 形象构件角色(Component):具体构件类和形象装璜者类的独特父类。
  2. 具体构件角色(ConcreteComponent):形象构件的子类,装璜者类能够给它减少额定的职责。
  3. 装璜角色(Decorator):形象构件的子类,具体装璜类的父类,用于给具体构件减少职责,但在子类中实现。
  4. 具体装璜角色(ConcreteDecorator):具体装璜类,定义了一些新的行为,向构件类增加新的个性。

二、入门案例

2.1、类图

2.2、根底类介绍

// 形象构件角色public interface Component {    void doSomeThing();}// 具体构件角色public class ConcreteComponent implements Component {    @Override    public void doSomeThing() {        System.out.println("解决业务逻辑");    }}// 装璜者类public abstract class Decorator implements Component {    private Component component;    public Decorator(Component component) {        this.component = component;    }    @Override    public void doSomeThing() {        // 调用解决业务逻辑        component.doSomeThing();    }}// 具体装璜类public class ConcreteDecorator extends Decorator {    public ConcreteDecorator(Component component) {        super(component);    }    @Override    public void doSomeThing() {        System.out.println("业务逻辑性能扩大");        super.doSomeThing();    }}

当然,如果须要扩大更多功能的话,能够再定义其余的ConcreteDecorator类,实现其余的扩大性能。
如果只有一个ConcreteDecorator类,那么就没有必要建设一个独自的Decorator类,而能够把Decorator和ConcreteDecorator的责任合并成一个类。

三、利用场景

  • 如风之前在一家保险公司干过一段时间。其中保险业务员也会在自家产品注册账号,进行采购。不过在这之前,他们须要经过培训,导入一张展业资格证书。而后再去采购保险产品供用户下单,本人则通过采购产生的业绩,参加分润,拿对应的佣金。
  • 对于下面导证书这个场景,实际上是会依据不同的保险产品,导入不同的证书的。并且证书的类型也不同,对应的解析、校验、执行的业务场景都是不同的。如何去实现呢?当然if-else的确也是一种不错的抉择。上面放一段伪代码
/** * @author 往事如风 * @version 1.0 * @date 2022/11/17 11:32 * @description */@RestController@RequestMapping("/certificate")public class CertificateController {    @Resource    private CommonCertificateService certificateService;    @PostMapping("/import")    public Result<Integer> importFile(@RequestParam MultipartFile file, @RequestParam String productCode) {        return Result.success(certificateService.importCertificate(file, productCode));    }}/** * @author 往事如风 * @version 1.0 * @date 2022/11/17 13:25 * @description */@Servicepublic class CommonCertificateService {    public Integer importCertificate(MultipartFile file, String productCode) {        // 1、参数非空校验        // 2、通过file后缀判断file类型,反对excel和pdf        // 3、解析file文件,获取数据,对立封装到定义的CertificatePojo类中        // 4、依据产品类型判断导入之前的业务逻辑        if (productCode.equals(DecorateConstants.PRODUCT_A)) {            // 从新计算业绩逻辑            // 从新算业绩类型逻辑            // 一坨坨代码去实现....        }        else if (productCode.equals(DecorateConstants.PRODUCT_B)) {            // 导入证书的代理人本人以及下级身份降职逻辑            // 业绩计算逻辑            // 一坨坨代码去实现...        } else if (productCode.equals(DecorateConstants.PRODUCT_C)) {            // c产品下的业务逻辑            // 一坨坨代码去实现...        } else {            // 默认的解决逻辑            // 一坨坨代码去实现...        }        // 5、证书数据保留        // 6、代理人信息保留        // 7、相干流水数据保留        // 返回代理人id        Integer agentId = Integer.MAX_VALUE;        return agentId;    }}

从下面的伪代码看到,所有的业务逻辑是在一起解决的,通过productCode去解决对应产品的相干逻辑。这么一看,如同也没故障,然而还是被技术大佬给否决了。好吧,如风决定重写。使用装璜者模式,重新处理下了下这段代码。
1、所有再从注解登程,自定义Decorate注解,这里定义2个属性,scene和type

  • scene:标记具体的业务场景
  • type:示意在该种业务场景下,定义一种具体的装璜器类

    /** * @author 往事如风 * @version 1.0 * @date 2022/11/8 17:44 * @description */@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Servicepublic @interface Decorate {   /**    * 具体的业务场景    * @return    */   String scene();   /**    * 类型:不同业务场景下,不同的装璜器类型    * @return    */   String type();}

    2、形象构件接口,BaseHandler,这个是必须滴

    /** * @author 往事如风 * @version 1.0 * @date 2022/11/8 17:07 * @description 形象解决接口 */public interface BaseHandler<T, R> {  /**   * 对立的解决办法   * @param t   * @return   */  R handle(T t);}

    3、形象装璜器类,AbstractHandler,持有一个被装璜类的援用,这个援用具体在运行时被指定

    /** * @author 往事如风 * @version 1.0 * @date 2022-11-13 22:10:05 * @desc 形象父类 */public abstract class AbstractHandler<T, R> implements BaseHandler<T, R> {  protected BaseHandler service;  public void setService(BaseHandler service) {      this.service = service;  }}

    4、具体的装璜器类AProductServiceDecorate,次要负责解决“导师证书”这个业务场景下,A产品相干的导入逻辑,并且标记了自定义注解Decorate,示意该类是装璜器类。次要负责对A产品证书导入之前逻辑的加强,咱们这里称之为“装璜”。

    /** * @author 往事如风 * @version 1.0 * @date 2022-11-13 23:11:16 * @desc */@Decorate(scene = SceneConstants.CERTIFICATE_IMPORT, type = DecorateConstants.PRODUCT_A)public class AProductServiceDecorate extends AbstractHandler<MultipartFile, Integer> {  /**   * 重写父类解决数据办法   * @param file   * @return   */  @Override  public Integer handle(MultipartFile file) {      // 解析      CertificatePojo data = parseData(file);      // 校验      check(data);      // 业绩计算      calAchievement(data.getMobile());      return (Integer) service.handle(data);  }  public CertificatePojo parseData(MultipartFile file) {      // file,证书解析      System.out.println("A产品的证书解析......");      CertificatePojo certificatePojo = new CertificatePojo();      certificatePojo.setMobile("12323");      certificatePojo.setName("张三");      certificatePojo.setMemberNo("req_343242ds");      certificatePojo.setEffectDate("2022-10-31:20:20:10");      return certificatePojo;  }  /**   * 证书数据校验   * @param data   */  public void check(CertificatePojo data) {      // 数据标准和重复性校验      // .....      System.out.println("A证书数据校验......");  }  /**   * 计算业绩信息   */  private void calAchievement(String mobile) {      System.out.println("查问用户信息, 手机号:" + mobile);      System.out.println("从新计算业绩...");  }}

    当然,还是其余装璜类,BProductServiceDecorateCProductServiceDecorate等等,负责装璜其余产品,这里就不举例了。
    5、当然还有治理装璜器类的装璜器类管理器DecorateManager,外部保护一个map,负责寄存具体的装璜器类

    /** * @author 往事如风 * @version 1.0 * @date 2022/11/15 17:18 * @description 装璜管理器 */public class DecorateManager {  /**   * 用于寄存装璜器类   */  private Map<String, AbstractHandler> decorateHandleMap = new HashMap<>();  /**   * 将具体装璜器类放在map中   *   * @param handlerList   */  public void setDecorateHandler(List<AbstractHandler> handlerList) {      for (AbstractHandler h : handlerList) {          Decorate annotation = AnnotationUtils.findAnnotation(h.getClass(), Decorate.class);          decorateHandleMap.put(createKey(annotation.scene(), annotation.type()), h);      }  }  /**   * 返回具体的装璜器类   *   * @param type   * @return   */  public AbstractHandler selectHandler(String scene, String type) {      String key = createKey(scene, type);      return decorateHandleMap.get(key);  }  /**   * 拼接map的key   * @param scene   * @param type   * @return   */  private String createKey(String scene, String type) {      return StrUtil.builder().append(scene).append(":").append(type).toString();  }}

    6、用了springboot,当然须要将这个管理器交给spring的bean容器去治理,须要创立一个配置类DecorateAutoConfiguration

    /** * @author 往事如风 * @version 1.0 * @date 2022-11-12 19:22:41 * @desc */@Configurationpublic class DecorateAutoConfiguration {  @Bean  public DecorateManager handleDecorate(List<AbstractHandler> handlers) {      DecorateManager manager = new DecorateManager();      manager.setDecorateHandler(handlers);      return manager;  }}

    7、被装璜的service类,CertificateService,只须要关注本人的外围逻辑就能够

    /** * @author 往事如风 * @version 1.0 * @date 2022/11/8 17:10 * @description 执行证书导入的service */@Servicepublic class CertificateService implements BaseHandler<CertificatePojo, Integer> {  /**   * 解决导入证书的外围逻辑service   * @param certificate   * @return   */  @Override  public Integer handle(CertificatePojo certificate) {      System.out.println("外围业务,证书数据:" + JSONUtil.toJsonStr(certificate));      // 1、证书数据保留      // 2、代理人信息保留      // 3、相干流水数据保留      // 其余的一些列外围操作      Integer agentId = Integer.MAX_VALUE;      // 返回代理人id      return agentId;  }}

    8、在原来的controller中,注入管理器类DecorateManager去调用,以及service,也就是被装璜的类。首先拿到装璜器,而后再通过setService办法,传入被装璜的service。也就是具体装璜什么类,须要在运行时才确定。

    /** * @author 往事如风 * @version 1.0 * @date 2022-11-13 23:30:37 * @desc */@RestControllerpublic class WebController {  @Resource  private DecorateManager decorateManager;  @Resource  private CertificateService certificateService;  @PostMapping("/import")  public Result importFile(@RequestParam MultipartFile file, @RequestParam String productCode) {      AbstractHandler handler = decorateManager.selectHandler(SceneConstants.CERTIFICATE_IMPORT, productCode);      if (Objects.isNull(handler)) {          return Result.fail();      }      handler.setService(certificateService);      return Result.success(handler.handle(file));  }}

    上面模仿下代理人导入证书的流程,当抉择A产品,productCode传A过去,后端的解决流程。

  • 对于A产品下,证书的解析,A产品传的是excel
  • 而后数据校验,这个产品下,特有的数据校验
  • 最初是外围的业绩重算,只有A产品才会有这个逻辑

    当抉择B产品,productCode传A过去,后端的解决流程。
  • 对于B产品下,证书的解析,A产品传的是pdf
  • 而后数据校验,跟A产品也不同,多了xxx步骤
  • 外围是代理人的降职解决,这部分是B产品独有的

    最初说一句,既然都用springboot了,这块能够写一个starter,做一个专用的装璜器模式。如果哪个服务须要用到,依赖这个装璜器的starter,而后标记Decorate注解,定义对应的scene和type属性,就能够间接应用了。

四、源码中使用

4.1、JDK源码中的使用

来看下IO流中,InputStreamFilterInputStreamFileInputStreamBufferedInputStream的一段代码

public abstract class InputStream implements Closeable {    public abstract int read() throws IOException;    public int read(byte b[], int off, int len) throws IOException {        if (b == null) {            throw new NullPointerException();        } else if (off < 0 || len < 0 || len > b.length - off) {            throw new IndexOutOfBoundsException();        } else if (len == 0) {            return 0;        }        int c = read();        if (c == -1) {            return -1;        }        b[off] = (byte)c;        int i = 1;        try {            for (; i < len ; i++) {                c = read();                if (c == -1) {                    break;                }                b[off + i] = (byte)c;            }        } catch (IOException ee) {        }        return i;    }}//--------------------------public class FilterInputStream extends InputStream {       protected FilterInputStream(InputStream in) {        this.in = in;    }    public int read() throws IOException {        return in.read();    }}//--------------------------public class BufferedInputStream extends FilterInputStream {    public BufferedInputStream(InputStream in, int size) {        super(in);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    }    public BufferedInputStream(InputStream in) {        this(in, DEFAULT_BUFFER_SIZE);    }        public int read() throws IOException {        return in.read();    }    public int read(byte b[], int off, int len) throws IOException {        return in.read(b, off, len);    }}//--------------------------public class FileInputStream extends InputStream {    public int read(byte b[]) throws IOException {        return readBytes(b, 0, b.length);    }}

再来看下这几个类的类图

这些类的代码有删改,能够看到BufferedInputStream中定义了很多属性,这些数据都是为了可缓冲读取来作筹备的,看到其有构造方法会传入一个InputStream的实例。理论编码如下

//被装璜的对象,文件输出流InputStream in=new FileInputStream("/data/log/app.log");//装璜对象,可缓冲InputStream bufferedIn=new BufferedInputStream(in);bufferedIn.read();

这里感觉很眼生吧,其实曾经使用了装璜模式了。

4.2、mybatis源码中的使用

在mybatis中,有个接口Executor,顾名思义这个接口是个执行器,它底下有许多实现类,如CachingExecutorSimpleExecutorBaseExecutor等等。类图如下:

次要看下CachingExecutor类,看着很眼生,很规范的装璜器。其中该类中的update是装璜办法,在调用真正update办法之前,会执行刷新本地缓存的办法,对原来的update做加强和扩大。

public class CachingExecutor implements Executor {  private final Executor delegate;  private final TransactionalCacheManager tcm = new TransactionalCacheManager();  public CachingExecutor(Executor delegate) {    this.delegate = delegate;    delegate.setExecutorWrapper(this);  }  @Override  public int update(MappedStatement ms, Object parameterObject) throws SQLException {    // 加强内容    // 批改办法就要清空本地的缓存    flushCacheIfRequired(ms);    // 调用原有的办法    return delegate.update(ms, parameterObject);  }}    

再来看下BaseExecutor类,这里有一个update办法,这个是本来的被装璜的update办法。而后再看这个本来的update办法,它调用的doUpdate办法是个形象办法,用protected润饰。咦,这不就是模板办法么,对于模板办法模式,这里就不开展赘述了。

public abstract class BaseExecutor implements Executor {  protected Executor wrapper;    @Override  public int update(MappedStatement ms, Object parameter) throws SQLException {    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());    if (closed) {      throw new ExecutorException("Executor was closed.");    }    clearLocalCache();    return doUpdate(ms, parameter);  }  protected abstract int doUpdate(MappedStatement ms, Object parameter)      throws SQLException;}

五、总结

长处

  1. 通过组合而非继承的形式,动静地扩大一个对象的性能,在运行时能够抉择不同的装璜器从而实现不同的性能。
  2. 无效的防止了应用继承的形式扩大对象性能而带来的灵活性差、子类无限度扩张的问题。
  3. 具体组件类与具体装璜类能够独立变动,用户能够依据须要新增具体组件类跟装璜类,在应用时在对其进行组合,原有代码毋庸扭转,合乎"开闭准则"。

毛病

  1. 这种比继承更加灵活机动的个性,也同时意味着更加多的复杂性。
  2. 装璜模式会导致设计中呈现许多小类 (I/O 类中就是这样),如果适度应用,会使程序变得很简单。
  3. 装璜模式是针对形象组件(Component)类型编程。然而,如果你要针对具体组件编程时,就应该从新思考你的利用架构,以及装璜者是否适合。

六、参考源码

编程文档:https://gitee.com/cicadasmile/butte-java-note利用仓库:https://gitee.com/cicadasmile/butte-flyer-parent