模板模式(Template Pattern)
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。也就是说在重复干一件事情的时候,在这些事情中主体的架构、骨架是一样的,只是每个事情有它自己的一些特性。就比如说我之前在开发君子签合同的时候,十几个合同。每隔合同的开发做的事情都是一样的。只是每个合同的内容,公司,章有所不同,但是合同的步骤都是一样的,这个时候,我们就可以使用模板模式。把签订合同的一系列步骤写一个抽象类,每个合同的类型这些,还有一些特性的参数就可以弄成抽象方法,让每个子类去重写。
特点:
1、一些方法通用,特性的东西让每个子类去重写。
2、将通用的算法、方法提取出来作为模板方法。
3、业务层调用的是模板方法。
4、关键代码在抽象类实现,其他步骤在子类实现。
优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景:
1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:
为防止恶意操作,一般模板方法都加上 final 关键词。
本人使用场景:在开发合同的时候,十几个合同每个合同的签订步骤都是一样的,我们就可以把签合同的这个事情作为一个模板方法,合同类型,合同上面的公司、法人、内容这些是不一样的,我们就可以把这些作为抽象的方法,让每个子类自己去实现自己特性的东西。
代码如下:
模板方法类:AbstractJunZiQianService
/**
*O和R是使用在类上面的泛型,O作为入参,R作为出参。因为公司是这样规定的,入参和出参都要放到实体里面
*/
public abstract class AbstractJunZiQianService<O extends JunZiQianBaseOrder, R extends JunZiQianBaseResult>
extends JunZiQianClientService {
private Logger log = LoggerFactory.getLogger(AbstractJunZiQianService.class);
@Autowired
private OrdOrderContractMapper ordOrderContractMapper;
public R execute(O order) {
// 初始化result(出参)
R result = initResult();
// 准备合同类型
JunZiQianContractTypeEnum contract = initContractType();
if (contract == null) {
result.setStatus(Status.FAIL);
result.setMessage("未设置合同类型!");
return result;
}
// 设置mdc
setMDC(order);
// 校验参数
order.check();
log.info("收到[{}]合同签署请求,请求参数:{}", contract.message(), order);
try {
// 设置默认值
if (result.getStatus() == null) {
result.setStatus(Status.SUCCESS);
}
if (result.getMessage() == null) {
result.setMessage("签署成功");
}
// 合同状态校验
OrdOrderContract ordOrderContract = ordOrderContractMapper.findByOrderIdAndContractType(order.getOrderId(), contract.code());
if (ObjectUtils.isEmpty(ordOrderContract)) {
ordOrderContract = new OrdOrderContract();
// 订单id
ordOrderContract.setOrderId(order.getOrderId());
// 合同名字
ordOrderContract.setContractName(contract.message());
// 合同类型
ordOrderContract.setContractType(contract);
// 合同编号
ordOrderContract.setContractNo(AppUtils.newOrderNo());
} else {
log.warn("[{}]已签署,请不要重复签署", contract.message());
throw new BizError("[" + contract.message() + "]已签署,请不要重复签署");
}
// 初始化请求头
ApplySignTmplRequest.Builder builder = initBuilder(contract.message(), contract.id());
// 根据合同设置个性化参数
setBuilder(builder, order, ordOrderContract.getContractNo());
// 创建客户端并发起请求,上线修改
ApplySignResponse response = getTestClient().applySignTmpl(builder.build());
//打印君子签日志
LogUtils.logResponse(response);
// 解析返回结果
if (response.isSuccess()) {
log.info("[{}]合同君子签成功", contract.message());
//君子签合同申请编号
ordOrderContract.setApplyNo(response.getApplyNo());
//成功
ordOrderContract.setContractStatus(StatusSwitchEnum.Y);
} else {
log.warn("[{}]合同君子签失败:{}", contract.message(), response.getError().getMessage());
throw new BizError("[" + contract.message() + "]签署失败:" + response.getError().getMessage());
}
// 保存合同信息
log.info("新增订单合同记录");
ordOrderContractMapper.insertSelective(ordOrderContract);
result.setContractNo(ordOrderContract.getContractNo());
} catch (BizError error) {
result.setStatus(Status.FAIL);
result.setMessage(error.getMessage());
} catch (Exception e) {
result.setStatus(Status.FAIL);
result.setMessage("系统繁忙,请稍后再试!");
log.error("[{}]合同签署发生异常:", e);
} finally {
if (result.isSuccess()) {
log.info("[{}]合同签署成功,结果:{}", contract.message(), result);
} else if (result.isProcessing()) {
log.info("[{}]合同签署中,结果:{}", contract.message(), result);
} else {
log.info("[{}]合同签署失败,原因:{},结果:{}", contract.message(), result.getMessage(), result);
}
//打印摘要日志
MDC.clear();
}
return result;
}
//初始化出参
protected abstract R initResult();
//获取合同类型
protected abstract JunZiQianContractTypeEnum initContractType();
//封装每个合同的一些特性东西,相当于就是封装我这个合同要显示的公司,法人信息这些东西
protected abstract void setBuilder(ApplySignTmplRequest.Builder builder, O order, String contractNo);
/**
* 设置MDC
*
* @param order
*/
private void setMDC(O order) {
if (order != null && StringUtils.isNotBlank(order.getSerialNo())) {
if (StringUtils.isNotBlank(MDC.get("serialNo"))) {
MDC.remove("serialNo");
}
MDC.put("serialNo", "-[serialNo:" + order.getSerialNo() + "]");
}
}
}
上面的这个类就是一个模板类,里面的execute()就是模板方法,所有的合同都是调用的这个生成方法。
然后看一下一个子类:DFB_ON_NOTDelegate 类
@Service
public class DFB_ON_NOTDelegate extends AbstractJunZiQianService<DFB_ON_NOTOrder, DFB_ON_NOTResult> {
//每一个合同的返回值实体类是不同的
@Override
protected DFB_ON_NOTResult initResult() {
return new DFB_ON_NOTResult();
}
//获取每个合同的类型
@Override
protected JunZiQianContractTypeEnum initContractType() {
return JunZiQianContractTypeEnum.DFB_ON_NOT;
}
//
@Override
protected void setBuilder(ApplySignTmplRequest.Builder builder, DFB_ON_NOTOrder order, String contractNo) {
// 4.构建模板动态参数
Map<String, Object> params = Maps.newHashMap();
params.put("contractNo", contractNo);//协议||合同编号
params.put("b2CompanyName", order.getB2CompanyName());//公司名称
params.put("contractDate", order.getContractDate());//合同签订日期
params.put("contractNoOfDFB_ON_NOT", order.getContractNoOfPAS_ON_NOT());//合同编号
params.put("b2LoanAmountCH", order.getB2LoanAmountCH());//转账金额大写
params.put("b2LoanAmount", order.getB2LoanAmount());//转账金额小写
params.put("loanRate", order.getLoanRate());//年化费率
params.put("loanPeriod", order.getLoanPeriod());//期限(天)
builder.withContractParams(params);
// 5.构建签约对象
HashSet<Signatory> signatories = Sets.newHashSet();
// 追加法人&公司的章信息
signatories.add(dphTestLegalSignatory());
signatories.add(dphTestCompanySignatory());
// 追加法人&公司的章信息
signatories.add(jiaHuaTestLegalSignatory());
signatories.add(jiaHuaTestCompanySignatory());
builder.withSignatories(signatories);
}
}
上面这个类就是每个合同自己的一些特性,params里面保存的就是合同上面要显示的东西
看一下对应的入参和出参
public class DFB_ON_NOTOrder extends JunZiQianBaseOrder {
private static final long serialVersionUID = -8837739717648845992L;
@NotBlank(message = "合同签订日期不能为空")
private String contractDate;
@NotBlank(message = "公司名称不允许为空")
private String b2CompanyName;
@NotBlank(message = "合同编号不能为空")
private String contractNoOfPAS_ON_NOT;
@NotBlank(message = "转账金额大写不允许为空")
private String b2LoanAmountCH;
@NotBlank(message = "转账金额小写不允许为空")
private String b2LoanAmount;
@NotBlank(message = "年化费率不允许为空")
private String loanRate;
@NotBlank(message = "期限(天)不能为空")
private String loanPeriod;
//get和set方法省略。。。
}
public class DFB_ON_NOTResult extends JunZiQianBaseResult {
private static final long serialVersionUID = 9125891038971985688L;
}
出参和入参就算没有什么属性,也要新建一个实体类,不能直接返回什么String,int之类的。
就这样,一个模板模式的例子就搞定了,只不过这样写的弊端你也能够想象出来。如果我有10个合同,那么我就要新建10个子类去继承模板类,新建10个实体类作为入参类,新建10个实体类作为出参类,新建的类会很多。但是的话也与好处,就是提高了代码的复用性和重用性,也便于维护。