设计模式 | 项目实战 | 模版模式
前言
本文含有以下技术点,不懂的小伙伴建议先去看看我之前准备的文章(我决不会承认是想着捞一手 <( ̄︶ ̄)> )
本文篇幅较长,希望小伙伴们能有耐心的慢慢品,滑太快了当心在看天书了
何为模版模式
在父类中定义一系列的处理流程,并实现其中公共的部分,将不同的部分下放到子类让子类实现
从 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.status
,AreaInfoDto
中使用了 mapId, rcsId
,这个对应于 AreaInfo
的 Map, 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 来个需要这么做,码着就给恶心吐了
然后就试着优化,迭代了很多个版本,这个算是最后的成品。我在这个模块上优化话的时间,估计够按最笨的思路写上好几回了,当然,也是值得的
套用了模版设计模式,在我们需要添加新类,并处理他们的转化关系时,直接继承抽象类,几行代码实现一下里面要处理的细节,就显得很优雅,不放肆的折腾,哪能有进步
像这一个模块,糅合了很多的知识点,比如使用泛型编程,用反射来动态处理数据,用设计模式来排版代码的功能布局
转载自:https://juejin.cn/post/7000690762757177374