设计模式 | 项目实战 | 模版模式

·  阅读 435
设计模式 | 项目实战 | 模版模式

这是我参与8月更文挑战的第 26 天,活动详情查看:8月更文挑战

前言

本文含有以下技术点,不懂的小伙伴建议先去看看我之前准备的文章(我决不会承认是想着捞一手 <( ̄︶ ̄)>

泛型

反射

本文篇幅较长,希望小伙伴们能有耐心的慢慢品,滑太快了当心在看天书了

何为模版模式

在父类中定义一系列的处理流程,并实现其中公共的部分,将不同的部分下放到子类让子类实现

图片.png

从 UML 来看,这个模式简单的不像话

代码模板

下面这套就是我为模板模式总结的代码结构,抽象类定义一件事情的处理方法,然后将一些细节封装为自己的内部实现(使的代码阅读会更有侧重点),如果有一些细节无法做到公共使用,需要根据特定的子类来实现,就定义为抽象的方法

抽象类和子类定义

abstract class AbstractSuper{
  public void doThing() {
    // do some things you like
    common();
    diff();
    // do other things
  }
  private void common() {}
  protected abstract void diff();
}
class ConcreteSub extends AbstractSuper{
  @Override
  protected void diff() {
  }
}
复制代码

客户端使用

AbstractSuper sup = new ConcreteSub();
sup.doThing();

sup = new ConcreteOtherSub();
sup.doThing();
复制代码

项目案例

需求

在我们的系统中,存在有 2 套数据模版,第一套数据模版,继承自 POJO,主要是用于系统内部的数据交互、流转,第 2 套数据模版,继承自 RcsDto (RCS Data Transfor Object),描述即为 Rcs 系统数据传输对象,用途为将本系统中的数据,转化为 Rcs 可使用的数据并发送过去,或者是接收 Rcs 发送的数据,转化为系统内部运转需要的数据结构

Pojo 及其实现类

@Data
public class Pojo implements Serializable {
  private Long id;
  private Integer status;
}
@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
@ToString
public class AreaInfo extends Pojo{
  private java.lang.String name;
  private java.lang.String encode;
  private com.rlzz.r9.rcs.po.RcsInfo rcs;
  private com.rlzz.r9.rcs.po.MapInfo map;
  private java.lang.String description;
  private java.lang.String rid = "";
}
复制代码

RcsDto 及其实现类

@Data
public class RcsDto {
  protected String id = "";
  // 状态码:0-正常,1-提交删除,2-已删除
  protected Integer stateCode = 0;
  // 用于身份校验
  protected APICheck check = APICheck.check;
}
@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
@ToString
public class AreaInfoDto extends RcsDto {
  private String mapId; // MapInfo
  private String rcsId; // RcsInfo
  private String name;// 区域名称
  private String encode; // 序号
  private String description;  // 备注
}
复制代码

数据转化的细节

仔细观察会发现,RcsDto.stateCode 对应与 Pojo.statusAreaInfoDto 中使用了 mapId, rcsId,这个对应于 AreaInfoMap, Rcs 对象

转化操作的抽象类

首先,我们要明确一点,我们是需要对 Pojo, RcsDto 他们对应的继承类进行数据转化操作,于是会在类上声明泛型操作 <P extends Pojo, D extends RcsDto>

简化版本

去掉细节,我们阅读这个抽象类的设计思路

首先,定义了 2 个类型互转的公共方法 D toDto(), P to Pojo()

在这两个公共方法中,会分别去调用对应的两个方法:

  • 私有的公共属性互转方法
    • 会在抽象类中以私有的方式,内部实现
  • 需要子类去实现的不同属性的互转方法
    • 定义为抽象类,交给子类负责
@SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class DataTemplate<P extends Pojo, D extends RcsDto> {
  public D toDto(P pojo, Class<D> dtoClz) {
    D dto = dtoClz.newInstance();

    setCommValPoToDo(pojo, dto);
    setDiffValPoToDo(pojo, dto);

    return dto;
  };

  public P toPojo(D dto, Class<P> pClz, Pojo... ps) {
    P pojo = pClz.newInstance();

    setDiffValDoToPo(pojo, dto, ps);
    setCommValDoToPo(pojo, dto);

    return pojo;
  }

  // po -> dto, 处理不同属性的值
  public abstract void setDiffValPoToDo(P info, D dto);

  // dto -> po, 处理不同属性的值
  public abstract void setDiffValDoToPo(P info, D dto, Pojo... ps);

  // po -> dto, 公共属性值转化
  private void setCommValPoToDo(P pojo, D dto) throws Exception {  }

  // dto -> po, 公共属性值转化
  private void setCommValDoToPo(P pojo, D dto) throws Exception {  }
}
复制代码
细节版本

上一个版本用于把控抽象类的一个设计思路,这一个版本就把故意忽略的细节全都摆了出来,建议仔细阅读代码中的注释

@SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class DataTemplate<P extends Pojo, D extends RcsDto> {
  public D toDto(P pojo, Class<D> dtoClz) {
    try {
      Class pClz = pojo.getClass();
      Field pField = null;
      D dto = dtoClz.newInstance();	// 初始化一个空白示例,后面用反射去给字段赋值

      setCommValPoToDo(pojo, dto);
      setDiffValPoToDo(pojo, dto);

      for (Field dField : FieldClass.getDtoFields().get(dtoClz)) {	// 在另一个地方处理好了可以进行互转操作的字段
        pField = FieldClass.getPojoFieldMap().get(pClz).get(dField.getName()); // 根据RcsDto的字段名,获取P的字段
        dField.set(dto, pField.get(pojo));// 为D的字段赋值P的字段值
      }

      return dto;
    } catch (Exception e) {
      LogUtil.exception(e);
    }
    return null; // 正常情况下会有返回数据
  };

  public P toPojo(D dto, Class<P> pClz, Pojo... ps) {	// 和上一步的转化类似
    if (dto != null) {
      try {
        P pojo = pClz.newInstance();	// 初始化 pojo 实例
        Class dClz = dto.getClass();
        Field dField = null;
        
        setDiffValDoToPo(pojo, dto, ps);
        setCommValDoToPo(pojo, dto);
        
        for (Field pField : FieldClass.getPojoFields().get(pClz)) {
          dField = FieldClass.getDtoFieldMap().get(dClz).get(pField.getName());
          pField.set(pojo, dField.get(dto));
        }
        return pojo;
      } catch (Exception e) {
        LogUtil.exception(e);
      }
    }
    return null; // 正常情况下会有返回数据
  }

  // po -> dto, 处理不同属性的值
  public abstract void setDiffValPoToDo(P info, D dto);

  // dto -> po, 处理不同属性的值
  public abstract void setDiffValDoToPo(P info, D dto, Pojo... ps);

  // po -> dto, 公共属性值转化,他们的父类上对应的字段值转化
  private void setCommValPoToDo(P pojo, D dto) throws Exception {
    Class supClz = dto.getClass().getSuperclass();
    Field dField = null, sField = null;
    // Pojo.rid -> Dto.id
    dField = pojo.getClass().getDeclaredField(Constant.RID);
    dField.setAccessible(true);
    sField = supClz.getDeclaredField(Constant.ID);
    sField.setAccessible(true);
    sField.set(dto, dField.get(pojo));
    // stateCode
    sField = supClz.getDeclaredField(Constant.STATE_CODE);
    sField.setAccessible(true);
    sField.set(dto, 0);
  }

  // dto -> po, 公共属性值转化,他们的父类上对应的字段值转化
  private void setCommValDoToPo(P pojo, D dto) throws Exception {
    Class pClz = pojo.getClass();
    Field dField = null, pField = null;
    // Dto.id -> Pojo.rid
    dField = dto.getClass().getSuperclass().getDeclaredField(Constant.ID);
    dField.setAccessible(true);
    pField = pClz.getDeclaredField(Constant.RID);
    pField.setAccessible(true);
    pField.set(pojo, dField.get(dto));
  }
}
复制代码

子类源码

子类实现

在这里,我们只需要处理好 pojo.Rcs <-> dto.rcsId, pojo.Map <-> dto.mapId 两个的映射关系

public class AreaV extends DataTemplate<AreaInfo, AreaInfoDto> {
  @Override
  public void setDiffValPoToDo(AreaInfo info, AreaInfoDto dto) {
    dto.setRcsId(info.getRcs().getRid());
    dto.setMapId(info.getMap().getRid());
  }
  @Override
  public void setDiffValDoToPo(AreaInfo info, AreaInfoDto dto, Pojo... ps) {
    info.setRcs((RcsInfo) ps[0]);
    info.setMap((MapInfo) ps[1]);
  }
} 
复制代码
添加静态方法优化

因为抽象类中的定义的转化流程,因为每个子类中引入的实体类是不一样的,引入的数量也是不一样的,必须使用 泛型+可变参 来处理

然后,使用的时候,就会发现一个很大的问题,new AreaV().toPojo(area, AreaInfo.class, Pojo... ps ??? ),我传递最后一项可变参,需要给哪些 Pojo 的继承类?给多少?按什么顺序给?这些问题,在我们刚编写代码的时候,脑子里多少还有点印象,但日子久了,估计就只有上帝知道(上帝:别瞎说啊,我也不知道 →_→)

趁着我们刚设计出来,还记得这些细节,直接封装为静态方法,对调用者来说,就更加友好了

@SuppressWarnings({ "unchecked", "rawtypes" })
public class AreaV extends DataTemplate<AreaInfo, AreaInfoDto> {
  private static DataTemplate<AreaInfo, AreaInfoDto> visit = new AreaV();

  public static AreaInfoDto infoToDto(AreaInfo info) {
    return visit.toDto(info, AreaInfoDto.class);
  }

  public static AreaInfo dtoToInfo(AreaInfoDto dto, RcsInfo rcs, MapInfo mapInfo) {
    return visit.toPojo(dto, AreaInfo.class, rcs, mapInfo);
  }

  public static AreaInfo dtoToInfo(AreaInfoDto dto) {
    return dtoToInfo(dto, null, null);
  }

  @Override
  public void setDiffValPoToDo(AreaInfo info, AreaInfoDto dto) {
    dto.setRcsId(info.getRcs().getRid());
    dto.setMapId(info.getMap().getRid());
  }

  @Override
  public void setDiffValDoToPo(AreaInfo info, AreaInfoDto dto, Pojo... ps) {
    info.setRcs((RcsInfo) ps[0]);
    info.setMap((MapInfo) ps[1]);
  }
}
复制代码

缺失的部分

其实,在这个案例中,还涉及到第 3 套数据模版,扁平化的 map 结构,以 AreaInfo 为例,他的 map 中的 key 如下,这种其实只需要考虑处理好他们的嵌套类就行,都没有需要放到子类去处理的特定细节,因篇幅就不再细讲了

id
status
name
encode
description
rid

rcsId
rcsName
rcsXXX ...

mapId
mapName
mapXXX ...
复制代码

吐槽

其实,最开始,我的设计就是直接在对应的 Pojo 类上添加 toDto(), toPageMap(), fromPageMap(Map),在对应的 Dto 类上添加 toPojo(),这么做,转化的效率极高,就是代码看着跟屎一样,实体类又会变的非常臃肿,代码的行数又要翻好几倍,而且,码起来非常的痛苦,有的类 20,30 个字段,手里面近 20 来个需要这么做,码着就给恶心吐了

然后就试着优化,迭代了很多个版本,这个算是最后的成品。我在这个模块上优化话的时间,估计够按最笨的思路写上好几回了,当然,也是值得的

套用了模版设计模式,在我们需要添加新类,并处理他们的转化关系时,直接继承抽象类,几行代码实现一下里面要处理的细节,就显得很优雅,不放肆的折腾,哪能有进步

像这一个模块,糅合了很多的知识点,比如使用泛型编程,用反射来动态处理数据,用设计模式来排版代码的功能布局

分类:
后端