在上一篇文章中,我们主要讲解了一种比较重要的设计模式,也就是耳熟能详、大名鼎鼎的代理模式。值得一提的是,代理模式特别重要,不管是在23种设计模式中还是在设计自己的应用程序时,代理模式占据的地位绝对是举足轻重的,希望大家一定要花时间好好理解它,好好掌握代理模式这种模式的精妙。
在本篇文章中,也就是接着上一篇文章,继续讲解一种全新的设计模式,那就是模板模式。回顾一下蒸包子的过程,包子分为很多类型,比如牛肉包子,豆沙包,猪肉韭菜包等等。但是呢,不管是什么类型的包子,它们所有经历的过程都是几乎一摸一样的。比加入面粉,加水,发面,揉面,加入配料,放入蒸笼,到包子出锅。针对蒸包子的过程,会发现所有的步骤几乎相差无几,也就是整个蒸包子的流程几乎是一模一样,唯一不同的地方就是在加入配料这个过程会有很大差距。比如牛肉包子加入牛肉,羊肉包子加入羊肉等等。
除此之外,在开发工作中,几乎大多数业务流程都是由三部分组合而成:
第一步是检验输入参数是否合法,
第二步为核心业务的开发,
第三步是插入日志,生成日志信息。
而对业务流程,所有大致步骤都是一模一样,但是每个子步骤呢,会有各自的差异。就像每个人的一生,都会经历生老病死,但是每个人在相同的阶段却能活出天差地别的差异,比如同样是人生,王思聪的每个阶段跟普通人肯定会产生很大的差异。
那么说了这么多,什么是模板模式呢?模板模式就是对于一些业务场景,整套工作骨架是确定的,但是呢对于各个子步骤,不同的业务又会具有不同的实现。简单的来说,就是整个执行流程或者说工作骨架一模一样,但是每个子步骤的实现方法会形成差异,这个时候就会考虑使用模板模式。其实也比较好理解,所谓模板,见名知意,就像写PPT或者写个人简历时,只需要去下载好一个符合自己的模板,然后确定模板后,比如第一部分写个人介绍,第二部分写自己的工作经历,第三部分写自己的教育经历。最后有模板后,把每一部分结合自己实际情况填充即可,而对不同的人来说,完成简历都是需要做这几个部分,但是对每个人来说,每个步骤每个部分又完全不是相同的,所以需要根据自己的实际去进行填充。
那么说完了模板模式的概念以及使用场景,接下来呢,我们就结合我案例场景,进行更为详细的讲解。
一、模板模式的实现
模板模式的实现并不算难,就拿上面蒸包子的案例进行实现,如果是你,你会怎么进行设计从而让我程序高可用高拓展呢。其实很简单,学习过面向对象,对于这个案例完全可以使用面向对象的知识进行完成,核心点就是通过继承以及多态的思想进行实现。
接下来对蒸包子这个过程进行抽象,对任何包子而言,除了加入配料的步骤会有很大差异,其它的子步骤都是一模一样的,所以呢,为了解决代码的冗余,可以定义一个包子的抽象类,将这些一模一样的子步骤维护在这个抽象类并给出实现即可。那么各种包子就对应着这个抽象类的具体实现子类,这样一来,所有类型的包子都会继承到这些子步骤。而对于加入配料这个步骤,因为它的实现是具体到每种包子或者说每个子类中的,所以这个步骤无法在父类中进行实现,这个时候呢,父类中将这个步骤定义为一个待实现的抽象方法即可。每种包子也就是每个子类在继承父类的时候完全可以根据自己的规则进行实现即可。当通过父类的引用指向子类的实现时,这个时候调用子类实现方法时,就会根据多态的规则在程序运行时执行子类重写的方法。
除此之外呢,在模板模式中,最重要的一项就是对每个步骤都会有严格流程控制,比如蒸包子,每个步骤的顺序肯定都是固定的,不可能在包子还没有包好或者没有加入配料的过程就直接放入蒸笼。所以说呢,在模板模式中存在一个核心的方法,叫做模板方法,模板方法的作用是用来组织和规范每个子步骤的调用顺序。当完成蒸包子这个过程,只需要调用这个模板方法即可。
说了这么多,我们就通过代码的方式来完成蒸包子这个过程。
第一步:创建蒸包子的抽象类,定义蒸包子的所有的子步骤方法;
package com.ignorance.templates;
public abstract class Steame {
public void addFlour(){
System.out.println("做包子第一步:加入面粉");
}
public void addWater(){
System.out.println("做包子第二步:加入水");
}
public void leavenDough(){
System.out.println("做包子第三步:发面");
}
public void kneadDough(){
System.out.println("做包子第四步:揉面");
}
//加入配料作为抽象方法,供子类进行实现
public abstract void addingIngredients();
public void putSteamer(){
System.out.println("做包子第六步:放入蒸笼");
}
public void outPot(){
System.out.println("做包子第七步:出锅");
}
}
在这个抽象类中,我们定义了七个基本方法,对应的也就是蒸包子的七个子步骤,对于六个已经实现的方法是因为所有蒸包子都是一模一样的,所以在父类中给出了具体实现,让每种特定具体的包子去继承即可。像加入配料方法也就是addingIngredients(),因为每种具体类型的包子加入配料的方式不一样,所以在父类中无法确定,所以需要将它的实现延迟到具体的子类中实现。
除此之外呢,上面说了模板模式有个很重要的方法,也就是模板方法,在包子父类里面只是初步定义了各个步骤方法,但是整个蒸包子的流程还需要通过模板方法串联起来,所以需要在包子父类中定义模板方法完成蒸包子整体工作流程的配置或者说是调用。如下所示:
第二步:创建模板方法progress(),用于维护当前算法的执行步骤:
package com.ignorance.templates;
public abstract class Steame {
final void progress(){
addFlour();
addWater();
leavenDough();
kneadDough();
addingIngredients();
putSteamer();
outPot();
}
public void addFlour(){
System.out.println("做包子第一步:加入面粉");
}
public void addWater(){
System.out.println("做包子第二步:加入水");
}
public void leavenDough(){
System.out.println("做包子第三步:发面");
}
public void kneadDough(){
System.out.println("做包子第四步:揉面");
}
//加入配料作为抽象方法,供子类进行实现
public abstract void addingIngredients();
public void putSteamer(){
System.out.println("做包子第六步:放入蒸笼");
}
public void outPot(){
System.out.println("做包子第七步:出锅");
}
}
在上述代码中,progress()方法则为模板模式中的模板方法,在该方法中,主要维护了做包子的步骤顺序,也从而规定了一个应用或者说是算法的运行步骤。因为每种具体的包子或者说每个子类都遵从这个规则,不管你是做牛肉包子还是韭菜包子,顺序肯定都是固定的,所以在父类里面进行规定即可,同样这个模板方法是不需要继承的,所以将其设置为final即可。
第三步:创建具体的包子实现类,比如第一种包子为豆沙包子,只需要继承父类并且是实现加入配料方法即可:
package com.ignorance.templates;
public class RedBeanBun extends Steame {
@Override
public void addingIngredients() {
System.out.println("做包子第五步:加入豆沙");
}
}
第四步:创建牛肉包子实现类,跟第三步豆沙包的实现逻辑一样,只需要重写加入配料方法即可:
package com.ignorance.templates;
public class BeefBun extends Steame {
@Override
public void addingIngredients() {
System.out.println("做包子第五步:加入牛肉");
}
}
第五步:进行测试,比如此时需要蒸豆沙包:
@Test
public void test01(){
Steame steame = new RedBeanBun();
steame.progress();
}
运行结果如下所示:
接下来呢,需要蒸牛肉包子,测试代码如下:
@Test
public void test02(){
Steame steame = new BeefBun();
steame.progress();;
}
运行结果如下:
通过上面的案例,发现模板模式其实就是使用继承加多态的思想进行实现的,我相信理解起来并不是很难。通过这种模式的设计,发现对于这一套流程来说,维护起来也比较轻松和优雅,比如再来个韭菜包子,只需要依葫芦画瓢,让其继承父类重写添加配料方法就可以轻松的完成韭菜包子的制作。
前面呢,重点介绍了模板模式维持算法执行顺序或者说定义整套流程规则的方法,也就是模板方法。在调用模板方法时会发现所有的包子都会按照这套流程走,但是如果此时有一个特殊的需求,比如某个顾客不喜欢吃馅儿呢,这就要求制作的包子不需要添加调料。那么在不破坏原有程序结构的基础之上该怎么进行优化呢,比如同样针对写简历,下载好模板后,有的兄弟是刚毕业的应届生,不具备工作经历,是不是就应该把工作经历这一栏删除掉呢。这个时候呢,就需要使用到模板模式中另外一个很重要的思想,叫作钩子函数。
钩子函数其实很简单,就是在整套流程中,有些子类具有特定的逻辑,父类需要提供一些拓展的方法让子类进行实现,在不改变原有模板方法的同时能够更大程度上拓展子类的功能。比如此时只需要执行该流程的若干个步骤,会舍弃掉一些步骤。那么在不破坏原有程序结构的同时,怎么动态的控制某些子步骤是否进行执行呢?
比如还是蒸包子这个过程,有的包子不需要添加馅儿,我相信有的同学直接可以说,可以创建一个子类同样实现抽象类,然后将配料方法实现,但是配料方法不写任何代码,虽然总体而言还是执行了七个步骤,但是配料方法没写具体代码,其实还是没有把任何调料添加到包子中去。这种方式来说是可以的,但是呢,我相信你一定觉得它有点怪怪的,好像是通过一种极其不优雅的方式解决了问题一般。那这时候这个方法是在子类中的,那么如果此时跳过的步骤是在父类中呢,比如新包子不需要揉面这个步骤,怎么办呢,我相信有同学同样可以说,同样可以利用多态的原则,将父类中实现的方法在子类重写,然后重写的方法不加任何逻辑,和上面一样,虽然执行了七个步骤,但是那个不需要执行的步骤虽然执行了,却什么事也没干。
说到这一步,需要将上面的父类改造一下,让其更符合面向对象的原则,因为刚开始那六个方法在父类给出了实现,是不需要子类进行实现的,所以将其设置为私有。如下所示:
package com.ignorance.templates;
public abstract class Steame {
final void progress(){
addFlour();
addWater();
leavenDough();
kneadDough();
addingIngredients();
putSteamer();
outPot();
}
private void addFlour(){
System.out.println("做包子第一步:加入面粉");
}
private void addWater(){
System.out.println("做包子第二步:加入水");
}
private void leavenDough(){
System.out.println("做包子第三步:发面");
}
private void kneadDough(){
System.out.println("做包子第四步:揉面");
}
//加入配料作为抽象方法,供子类进行实现
public abstract void addingIngredients();
private void putSteamer(){
System.out.println("做包子第六步:放入蒸笼");
}
private void outPot(){
System.out.println("做包子第七步:出锅");
}
}
那么经过这么一番简单的改造,那么上一种想法是不是就存在问题了,如果跳过的步骤是动态改变的步骤还行,因为它是延迟在具体的子类进行实现的。子类可以自己自定义实现,如果是这些固定的步骤,为了减少程序的冗余代码,是将其声明在父类中的,这个时候虽然子类都可以进行继承,但是因为对应的方法是私有的,所以在子类中,并不能操作其对应的方法。这个时候呢,不得不放弃这种奇怪的思想,这个时候就需要使用到模板模式中的钩子函数。
接下来呢,比如有种特殊的包子,它不需要进行揉面操作以及添加调料,应该怎么改造程序呢?如下所示:
package com.ignorance.templates;
public abstract class Steame {
final void progress(){
addFlour();
addWater();
if (isLeavenDough()){
leavenDough();
}
kneadDough();
if (isAddingIngredients()){
addingIngredients();
}
putSteamer();
outPot();
}
private void addFlour(){
System.out.println("做包子第一步:加入面粉");
}
private void addWater(){
System.out.println("做包子第二步:加入水");
}
private void leavenDough(){
System.out.println("做包子第三步:发面");
}
private void kneadDough(){
System.out.println("做包子第四步:揉面");
}
//加入配料作为抽象方法,供子类进行实现
public abstract void addingIngredients();
private void putSteamer(){
System.out.println("做包子第六步:放入蒸笼");
}
private void outPot(){
System.out.println("做包子第七步:出锅");
}
protected boolean isLeavenDough(){
return true;
}
protected boolean isAddingIngredients(){
return true;
}
}
上述代码我相信同学们一看就懂,是定义了两个返回值为布尔类型的方法,然后在模板方法的对应步骤上进行判断,如果对应的返回值为true,那么该子步骤就会进行实现,如果为false,那么也就会跳过。在这里呢,这两个方法就叫做模板模式中的钩子函数,都说钩子函数能够动态且灵活的拓展特定子类的功能,那么动态体现在哪儿呢?
就比如现在的代码,在父类中默认返回值就是true,那么原则上此时所有的包子七个步骤都会进行执行,那么怎么动态控制某些子类不执行这两个步骤呢,那么其实这个动态还是体现在多态。如果有些子类不需要执行,是不是可以直接重写钩子函数,改变其返回值,不就可以轻松的实现某些子步骤不进行执行了吗?比如此时有种特殊的包子不需要发面以及添加调料:
package com.ignorance.templates;
public class SpecialBun extends Steame {
@Override
public void addingIngredients() {
}
@Override
protected boolean isLeavenDough() {
return false;
}
@Override
protected boolean isAddingIngredients() {
return false;
}
}
可以看出对这种特殊的包子而言,是重写了这两个钩子函数的,那么在调用父类的模板方法时,根据多态的原则,判断的时候就会调用子类重写的钩子函数,此时返回值为false,那么在不改变原有模板方法执行结构的同时,就轻松的实现了跳过其步骤的功能。
接下来了,进行测试:
@Test
public void test03(){
Steame steame = new SpecialBun();
steame.progress();
}
运行结果如下图所示:
通过上面的案例,可以看出模板模式还是挺简单的,它主要是一种把继承和多态发挥到淋漓尽致的一种设计模式,适用于在业务流程和骨架确定时,而某些子步骤实现是很大差异的场景下。
二、利用模板模式优化代码
前几天,笔者使用微服务完成了一个系统。都知道在单个微服务中,其实最简单的结构也就是三层架构,即控制层->业务层->数据访问层。在业务层往往会写很多逻辑比较复杂的代码,通常来说一个Service代码会很多,再看起来也比较麻烦,维护起来看代码也比较头疼,就比如我前几天写得一个关于一个提交试题然后算分的逻辑,代码也不算多,看一下,整个步骤:
第一层:是的控制层,也就是Controller层:
@PostMapping("/submitRiskResult")
public RetVal submitRiskResult(@RequestBody List<RiskSubmitVo> riskSubmitVoList, HttpServletRequest request){
String userId= AuthContextHolder.getUserId(request);
Integer currentUserId = 0;
if (StringUtils.isNotBlank(userId)){
currentUserId = Integer.parseInt(userId);
}
Map<String,Object> returnMap = riskService.submitRiskResult(riskSubmitVoList,currentUserId);
return RetVal.ok(returnMap).message("恭喜您已完成风评!");
}
在控制层呢,我们主要是接受到客户端提交的作答参数,取出当前用户的id,然后调用业务层完成风评试题的提交。
第二层:是我们的业务层,代码如下:
public Map<String, Object> submitRiskResult(List<RiskSubmitVo> riskSubmitVoList, Integer userId) {
Boolean isBinded = userFeignClient.isBinding(userId);
if (!isBinded){
throw new RaiseException(RetValCodeEnum.CURRENT_USER_NOT_BINDED);
}
LambdaQueryWrapper<UserRisk> userRiskLambdaQueryWrapper = new LambdaQueryWrapper<>();
userRiskLambdaQueryWrapper.eq(UserRisk::getUserId,userId);
List<UserRisk> userRiskList = userRiskMapper.selectList(userRiskLambdaQueryWrapper);
if (!userRiskList.isEmpty()){
throw new RaiseException(RetValCodeEnum.RISK_RECORD_EXISTS);
}
List<Map<String, Object>> riskPointList = riskSubmitVoList.stream().map(o ->
{
Map<String, Object> map = new HashMap<>();
map.put("id", o.getRiskId());
map.put("selectedItem", o.getAnswer());
Risk risk = baseMapper.selectById(o.getRiskId());
Integer point = o.getAnswer().startsWith("A") ? risk.getPoint1() :
o.getAnswer().startsWith("B") ? risk.getPoint2() :
o.getAnswer().startsWith("C") ? risk.getPoint3() :
o.getAnswer().startsWith("D") ? risk.getPoint4() : 0;
map.put("point",point);
return map;
}).collect(Collectors.toList());
Integer score = riskPointList.stream().
mapToInt(o -> Integer.parseInt(o.get("point").toString())).sum();
RiskResult riskResult = new RiskResult();
riskResult.setId(null);
riskResult.setUserId(userId);
riskResult.setScore(score);
riskResult.setCreatetime(new Date());
riskResultMapper.insert(riskResult);
List<RiskResultDetail> riskResultDetailList = new ArrayList<>();
riskPointList.forEach(o -> {
Risk risk = baseMapper.selectById(Integer.parseInt(o.get("id").toString()));
Field[] declaredFields = risk.getClass().getDeclaredFields();
String selectedItem = null;
Field selectedItemField = Arrays.stream(declaredFields).filter(e -> {
e.setAccessible(true);
try {
return e.get(risk).toString().startsWith(o.get("selectedItem").toString());
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
return false;
}).collect(Collectors.toList()).get(0);
try {
selectedItem = selectedItemField.get(risk).toString();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
RiskResultDetail riskResultDetail = new RiskResultDetail(null,Integer.parseInt(o.get("id").toString()),
selectedItem,Integer.parseInt(o.get("point").toString()),new Date(),riskResult.getId());
riskResultDetailList.add(riskResultDetail);
});
if (!riskResultDetailList.isEmpty()){
riskResultDetailService.saveBatch(riskResultDetailList);
}
RetVal retVal = userFeignClient.updatePersonalIntegral(userId, score);
if (!retVal.isOk()){
throw new RaiseException(RetValCodeEnum.ROMOTE_SERVICE_ERROR);
}
LambdaQueryWrapper<RiskLevel> levelLambdaQueryWrapper = new LambdaQueryWrapper<>();
levelLambdaQueryWrapper.le(RiskLevel::getStartPoint,score);
List<RiskLevel> riskLevelList = riskLevelMapper.selectList(levelLambdaQueryWrapper);
OptionalInt max = riskLevelList.stream().mapToInt(o -> o.getStartPoint()).max();
RiskLevel riskLevel = riskLevelList.stream().filter(o -> max.getAsInt() == o.getStartPoint()).collect(Collectors.toList()).get(0);
UserRisk userRisk = new UserRisk();
userRisk.setUserId(userId);
userRisk.setRiskLevel(riskLevel.getRiskLevel());
userRisk.setCreateTime(new Date());
userRiskMapper.insert(userRisk);
Map<String,Object> returnMap = new HashMap<>();
returnMap.put("score",score);
returnMap.put("riskLevel",riskLevel.getDescript());
return returnMap;
}
其实代码不算多,核心步骤可以归纳为:
第一步:做参数检验;
第二步:校验做题情况,以及算出最终得分;
第三步:则是持久化,将本次结果保存结果到数据库,并且返回响应参数;
像这种情况总结出步骤后,就可以考虑对代码进行优化,让每个步骤专门只做自己负责的事情,到时候维护起来只需要修改具体的某个子步骤就行,在代码可读性上还是有一定的提高。
接下来呢,使用模板模式进行优化,让代码看起来更加简介:
第一步:创建抽象父类,根据抽取的三个步骤定义三个子步骤:
package com.ignorance.raise.risk.service;
import com.ignorance.raise.model.vo.RiskSubmitVo;
import java.util.List;
import java.util.Map;
public abstract class RiskAbStractService {
//第一步:检验业务参数
protected abstract void chechBusiness(Integer userId);
//第二步:执行核心业务方法
protected abstract Map<String,Object> business(List<RiskSubmitVo> riskSubmitVoList, Integer userId);
//第三步:持久化数据库并且返回响应参数
protected abstract Map<String,Object> saveAndpersistence(Map<String,Object> paramMap);
}
第二步:创建模板方法service(),用于控制程序的执行流程:
package com.ignorance.raise.risk.service;
import com.ignorance.raise.model.vo.RiskSubmitVo;
import java.util.List;
import java.util.Map;
public abstract class RiskAbStractService {
public Map<String,Object> service(List<RiskSubmitVo> riskSubmitVoList, Integer userId){
chechBusiness(userId);
Map<String,Object> returnMap = business(riskSubmitVoList,userId);
return saveAndpersistence(returnMap);
}
//第一步:检验业务参数
protected abstract void chechBusiness(Integer userId);
//第二步:执行核心业务方法
protected abstract Map<String,Object> business(List<RiskSubmitVo> riskSubmitVoList, Integer userId);
//第三步:持久化数据库并且返回响应参数
protected abstract Map<String,Object> saveAndpersistence(Map<String,Object> paramMap);
}
第三步:创建子类实现类,实现各个子步骤方法,让每个步骤各司其职:
package com.ignorance.raise.risk.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ignorance.raise.client.UserFeignClient;
import com.ignorance.raise.exception.RaiseException;
import com.ignorance.raise.model.*;
import com.ignorance.raise.model.vo.RiskSubmitVo;
import com.ignorance.raise.result.RetVal;
import com.ignorance.raise.result.RetValCodeEnum;
import com.ignorance.raise.risk.mapper.*;
import com.ignorance.raise.risk.service.RiskAbStractService;
import com.ignorance.raise.risk.service.RiskResultDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class RiskTemplatesService extends RiskAbStractService {
@Resource
private RiskResultMapper riskResultMapper;
@Resource
private RiskResultDetailMapper riskResultDetailMapper;
@Resource
private RiskResultDetailService riskResultDetailService;
@Autowired
private UserFeignClient userFeignClient;
@Resource
private UserRiskMapper userRiskMapper;
@Resource
private RiskLevelMapper riskLevelMapper;
@Resource
private RiskMapper riskMapper;
@Override
protected void chechBusiness(Integer userId) {
Boolean isBinded = userFeignClient.isBinding(userId);
if (!isBinded){
throw new RaiseException(RetValCodeEnum.CURRENT_USER_NOT_BINDED);
}
LambdaQueryWrapper<UserRisk> userRiskLambdaQueryWrapper = new LambdaQueryWrapper<>();
userRiskLambdaQueryWrapper.eq(UserRisk::getUserId,userId);
List<UserRisk> userRiskList = userRiskMapper.selectList(userRiskLambdaQueryWrapper);
if (!userRiskList.isEmpty()){
throw new RaiseException(RetValCodeEnum.RISK_RECORD_EXISTS);
}
}
@Override
protected Map<String, Object> business(List<RiskSubmitVo> riskSubmitVoList, Integer userId) {
Map<String,Object> resultMap = new HashMap<>();
List<Map<String, Object>> riskPointList = riskSubmitVoList.stream().map(o ->
{
Map<String, Object> map = new HashMap<>();
map.put("id", o.getRiskId());
map.put("selectedItem", o.getAnswer());
Risk risk = riskMapper.selectById(o.getRiskId());
Integer point = o.getAnswer().startsWith("A") ? risk.getPoint1() :
o.getAnswer().startsWith("B") ? risk.getPoint2() :
o.getAnswer().startsWith("C") ? risk.getPoint3() :
o.getAnswer().startsWith("D") ? risk.getPoint4() : 0;
map.put("point",point);
return map;
}).collect(Collectors.toList());
Integer score = riskPointList.stream().
mapToInt(o -> Integer.parseInt(o.get("point").toString())).sum();
RiskResult riskResult = new RiskResult();
riskResult.setId(null);
riskResult.setUserId(userId);
riskResult.setScore(score);
riskResult.setCreatetime(new Date());
List<RiskResultDetail> riskResultDetailList = new ArrayList<>();
riskPointList.forEach(o -> {
Risk risk = riskMapper.selectById(Integer.parseInt(o.get("id").toString()));
Field[] declaredFields = risk.getClass().getDeclaredFields();
String selectedItem = null;
Field selectedItemField = Arrays.stream(declaredFields).filter(e -> {
e.setAccessible(true);
try {
return e.get(risk).toString().startsWith(o.get("selectedItem").toString());
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}
return false;
}).collect(Collectors.toList()).get(0);
try {
selectedItem = selectedItemField.get(risk).toString();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
RiskResultDetail riskResultDetail = new RiskResultDetail(null,Integer.parseInt(o.get("id").toString()),
selectedItem,Integer.parseInt(o.get("point").toString()),new Date(),riskResult.getId());
riskResultDetailList.add(riskResultDetail);
});
RetVal retVal = userFeignClient.updatePersonalIntegral(userId, score);
if (!retVal.isOk()){
throw new RaiseException(RetValCodeEnum.ROMOTE_SERVICE_ERROR);
}
LambdaQueryWrapper<RiskLevel> levelLambdaQueryWrapper = new LambdaQueryWrapper<>();
levelLambdaQueryWrapper.le(RiskLevel::getStartPoint,score);
List<RiskLevel> riskLevelList = riskLevelMapper.selectList(levelLambdaQueryWrapper);
OptionalInt max = riskLevelList.stream().mapToInt(o -> o.getStartPoint()).max();
RiskLevel riskLevel = riskLevelList.stream().filter(o -> max.getAsInt() == o.getStartPoint()).collect(Collectors.toList()).get(0);
UserRisk userRisk = new UserRisk();
userRisk.setUserId(userId);
userRisk.setRiskLevel(riskLevel.getRiskLevel());
userRisk.setCreateTime(new Date());
resultMap.put("riskResult",riskResult);
resultMap.put("riskResultDetailList",riskResultDetailList);
resultMap.put("userRisk",userRisk);
resultMap.put("score",score);
resultMap.put("riskLevel",riskLevel.getDescript());
return resultMap;
}
@Override
protected Map<String, Object> saveAndpersistence(Map<String, Object> paramMap) {
RiskResult riskResult = (RiskResult)paramMap.get("riskResult");
riskResultMapper.insert(riskResult);
List<RiskResultDetail> riskResultDetailList = (List<RiskResultDetail>)paramMap.get("riskResultDetailList");
if (!riskResultDetailList.isEmpty()){
riskResultDetailService.saveBatch(riskResultDetailList);
}
UserRisk userRisk = (UserRisk)paramMap.get("userRisk");
userRiskMapper.insert(userRisk);
Map<String,Object> returnMap = new HashMap<>();
returnMap.put("score",paramMap.get("score"));
returnMap.put("riskLevel",paramMap.get("riskLevel"));
return returnMap;
}
}
第四步:在我们的控制层完成对模板方法的调用:
@PostMapping("/submitRiskResult")
public RetVal submitRiskResult(@RequestBody List<RiskSubmitVo> riskSubmitVoList, HttpServletRequest request){
String userId= AuthContextHolder.getUserId(request);
Integer currentUserId = 0;
if (StringUtils.isNotBlank(userId)){
currentUserId = Integer.parseInt(userId);
}
Map<String,Object> returnMap = riskAbStractService.service(riskSubmitVoList,currentUserId);
return RetVal.ok(returnMap).message("恭喜您已完成风评!");
}
以上呢,就是将之前的代码通过模板模式进行了改造,其实吧这个案例并不算很成功,像模板模式更适合所有的业务都遵从某个流程控制,然后只需要定义某个规范类,提供各个子步骤的抽象方法,在规范类中定义流程控制即可。整体来说,每个子步骤各司其职,将一个很复杂的业务转化为多个子步骤进行完成,无论是流程发生改变还是项目的整体维护都能够提高一定的效率,比如某个业务的输入校验出现了问题,整体业务假如是20000行代码,不需要在20000行代码寻找,只需要它的子步骤去检查即可,相当于来说还是挺不错,能够在一定程度上提高工作效率的。
三、Spring中使用模板模式的案例
在上面的讲解中呢,主要讲解了模板模式的核心,其本质就是通过继承加多态的方式进行实现的。然后呢,通过蒸包子这个小案例对模板模式进行了简单的实现,结合这个小案例对模板模式的核心,无论是制定调用规则的模板方法还是动态执行某些步骤的钩子函数都做了比较详细的讲解。最后呢,通过一个开发的简单案例,将其使用模板模式的思想进行了改造,之所以会讲这么一些案例,是希望大家更深一步的理解和掌握到模板模式的核心,从而对其灵活应用,其实可以负责任的说,模板模式是对面向对象的灵活应用,掌握了模板模式的思想,无论是在进行程序设计还是理解封装和继承都会带来很大的帮助。
以上的案例都是自己折腾的,接下来看一下源码中对模板模式这种思想的使用,知道Java的半壁江山就是Spring框架,正是因为Spring框架的横空出世,才会让Java这门语言经久不衰。Spring作为一个极其优秀的框架,其底层也是涉及到大量使用设计模式的场景。接下来一起来研究一下Spring底层使用到模板模式这种设计思想的场景。
IOC容器初始化时使用到了模板模式:
第一步:在Spring中存在一个ConfigurableApplicationContext类,该类是IOC容器的一个父类。如下图所示:
第二步:在ConfigurableApplicationContext类中,定义了一个refresh()方法,这个方法也是初始化IOC容器的主要核心方法,不妨看一下这个方法长什么样子:
可以看出在这个类中,refresh()方法定义的为一个抽象方法,具体实现是在它的各个子类中,接下来重点看它其中一个叫作AbstractApplicationContext的子类。
在AbstractApplicationContext中,对父类的refresh()方法进行了重写,如下图所示:
首先不用明白每个步骤的意思,看到这个方法的整体结构,是不是特别熟悉,这个方法是不是跟我们前面蒸包子的progress()以及代码优化中的service()方法结构上简直一模一样,所以这个方法为Spring在加载IOC容器时的模板方法,该方法规定了Spring加载IOC容器各个子步骤的执行顺序:
第一步:prepareRefresh方法,刷新前的准备工作,比如:设置容器启动时间,设置活跃状态为true,设置关闭状态为false,获取environment对象,并加载当前的属性值到environment对象中,准备监听器和事件的集合对象,默认为空的集合;
第二步:obtainFreshBeanFactory,主要是创建容器对象(DefaultListableBeanFactory),生成BeanDefination,这个时候DefaultListableBeanFactory中的beanDefinationMap和beanDefinationName当中会有值。这里会进行xml文件解析,bean标签解析,创建beanDefination对象;
第三步:prepareBeanFactory(beanFactory),创建了beanfactory,但它当中很多属性都是为null,所以这个方法主要是对beanFactory属性进行填充;
第四步:postProcessBeanFactory(beanFactory),对bean工厂进行一些处理,比如添加beanFactoryPostProcess,由具体的子类去实现;
第五步:invokeBeanFactoryPostProcessors(beanFactory),调用执行各种beanFactory后置处理器;
第六步:registerBeanPostProcessors(beanFactory),注册BeanPostProcessor,只注册,不调用,等初始化的时候再调用;
第七步:initMessageSource,初始化MessageSource组件(做国际化功能;消息绑定,消息解析),这个接口提供了消息处理功能。主要用于国际化/i18n;
第八步:initApplicationEventMulticaster,多播器:方便后面发布监听事件。在Spring容器中初始化事件广播器,事件广播器用于事件的发布。程序首先会检查bean工厂中是否有bean的名字和这个常量(applicationEventMulticaster)相同的,如果没有那么就使用默认的ApplicationEventMulticaster的实现:SimpleApplicationEventMulticaster;
第九步:onRefresh,子类去实现。一个模板方法,不同的Spring容器做不同的事情。比如web程序的容器ServletWebServerApplicationContext中会调用createWebServer方法去创建内置的Servlet容器;
第十步:registerListeners,注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean,这些监听器是注册到ApplicationEventMulticaster中的。这不会影响到其它监听器bean。在注册完以后,还会将其前期的事件发布给相匹配的监听器;
第十一步:finishBeanFactoryInitialization(beanFactory),实例化剩下的(非懒加载)单例。循环beanDefinationNames,通过beanName从BeanDefinationMap中拿到beanDefination,实例化bean的时候,我们先调用getBean(beanName)方法,从缓存中查,看缓存中有没有,如果没有,我们则要doCreateBean,doCreateBean通过反射调用bean的构造方法创建出bean,创建出来以后会放入到三级缓存当中,然后填充属性populateBean,填充完以后调用initializeBean方法初始化,这个方法里面看你是否实现了Aware接口,如果实现了就执行Aware接口方法,然后调用BeanPostProcessor:postProcessBeforeInitialization方法,接着执行init方法,然后再调用BeanPostProcessor:postProcessAfterInitialization方法,完成初始化,然后返回对象实例,把它设置到一级缓存当中。
第十二步:finishRefresh,完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)。
通过上面的分析,可以清楚的知道AbstractApplicationContext类中refresh()方法是控制IOC容器初始化的核心方法,它是IOC容器初始化真正的流程控制者,所以它就是模板模式的模板方法。
接下来呢,再看一下在这些子步骤中哪些方法为抽象方法,即需要延迟到具体的子类进行实现的方法。
我们看一下图中标志红裤的子步骤。也就是初始化IOC容器的第二个子步骤,可以看到方法名叫作obtainFreshBeanFactory();
我们不妨进去这个方法的内部:
可以很清楚的看出这个方法调用了两个方法,一个为在当前类声明的refreshBeanFactory()方法,其次则为getBeanFactory();接下来再继续进入这两个方法内部:
可以看出这两个方法都是抽象方法,就和蒸包子中添加配料的方法类似,在父类里面将其声明为抽象方法,具体实现延迟到了具体的子类中。
最后呢,看一下在模板模式中另外一个很重要的主体,也就是钩子函数,那么在这些子步骤中,是否存在钩子函数呢。其实是有的,比如:这两个方法,如下图所示:
一个叫作postProcessBeanFactory(),一个叫作onRefresh()。接下来进入两个方法内部:如下图所示:
可以这两个方法在父类中并没有实现,但并不是抽象方法就相当于什么都没做,这两个方法主要是提供给子类,让子类根据自己的业务需要进行拓展的方法,所以它也就是当前模板模式中的钩子方法。
总结
在本篇文章中,讲解了一种全新的设计模式,叫作模板模式,从文章的开始从生活的一些小案例引入了模板模式的概念,以及模板模式主要是用来解决什么类型问题的。
通过以上的分析呢,可以得出结论模板模式适用于某个算法或者某套业务流程以及骨架是确定的,组成这套流程的若干个子步骤会根据不同的子类进行变化,这个时候就可以将程序使用模板模式的思想进行设计。组织规则以及约定步骤的方法称之为模板方法,除此之外模板模式也存在若干个钩子函数供子类根据自己需要进行灵活调用,而不用非要使用整套模板约定。
通过以上的分析呢,我们发现模板模式同样也是一种比较重要的设计模式,在我Spring框架中也存在着大量的使用,之前都说任何框架的底层都离不开设计模式加反射,如果不甘做码农,学习设计模式是比较有必要的,也希望大家能够真正理解并且掌握设计模式的精髓,在不断使用的场景下将其融会贯通,成为一个更强更优秀的自己。