一、模板方法模式是什么?有哪些应用场景?
- 所谓模板方法模式,就是定义一个操作中的算法的骨架(这个将具体逻辑步骤汇总起来的方法就是所谓的模板方法),而将一些步骤延迟到子类中去实现
- 模板方法模式是类的行为模式,它的应用场景是同一个接口的不同实现类存在公共代码,这样就可以用模板方法模式将不变部分进行实现、封装在模板类中,将可变部分作为抽象方法留给子类扩展,从而复用顶级逻辑的代码。
二、模板方法模式的实现
- 模板方法模式的实现,关键代码是定义一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现
- 为防止恶意操作,一般模板方法都加上 final 关键词,可以避免子类重写模板方法
三、模板方法模式的应用举例
- 使用模板方法模式统一调用外部服务接口
import com.success.uniformApiInvoker.req.OuterBaseRequest;
import com.success.uniformApiInvoker.res.OuterBaseResponse;
import com.success.util.HttpClientUtil;
/**
* @Title:调用外部服务基类
* @Author:wangchenggong
* @Date 2020/9/25 18:42
* @Description
* @Version
*/
public abstract class AbstractInvokeOuterSystemService<T extends OuterBaseRequest,R extends OuterBaseResponse> implements InvokeOuterSystemService<T,R>{
@Override
public R request(T t) throws Exception {
//1. 创建请求报文(不用的服务接口可能要进行的入参校验等,需要子类实现)
String reqMessage = this.createMessage(t);
//2. 发送请求报文(这个步骤可以共用)
String respMessage = HttpClientUtil.doPostJson("http://url", reqMessage);
//3. 将响应报文解析为响应信息对象(不用的服务接口需要响应不同的信息,需要子类实现)
R resp = this.parseResponse(respMessage);
return resp;
}
protected abstract String createMessage(T t);
protected abstract R parseResponse(String responseMsg);
}
- 使用模板方法模式对外提供的统一接口服务
import com.alibaba.fastjson.JSONObject;
import com.success.uniformApiProvider.req.BaseRequest;
/**
* @Title:对外提供的统一接口服务基类
* @Author:wangchenggong
* @Date 2020/9/26 8:12
* @Description JSONObject是用来明确FacadeApiService<T>中的泛型类型,而AbstractFacadeApiService<T extends BaseRequest,R>中的泛型类型需要子类明确
* @Version
*/
public abstract class AbstractFacadeApiService<T extends BaseRequest,R> implements FacadeApiService<JSONObject>{
/**
* 处理业务请求
* @param requestMessage 请求报文对象(这个请求报文对象需要在Controller中对请求json报文做初步的解析,才可以得到)
* @return
*/
@Override
public String handle(RequestMessage<JSONObject> requestMessage) {
//这里可以做一些入参校验、验签等,主要针对请求头信息
//1. 解析请求体信息
T t = this.parseJSONObject(requestMessage.getData());
//2. 执行业务逻辑(不同的服务接口处理逻辑不同,所以)
ResponseMessage<R> responseMessage = this.doHandle(t);
//3.将响应信息序列化为json串(此步骤可以共用)
String responseMsg = JSONObject.toJSONString(responseMessage);
return responseMsg;
}
protected abstract ResponseMessage<R> doHandle(T req);
protected abstract T parseJSONObject(JSONObject jsonObject);
}
- 使用模板方法模式实现通知服务
import com.alibaba.fastjson.JSON;
import com.success.notice.vo.NoticeMessage;
import com.success.notice.vo.NoticeResult;
import com.success.util.HttpClientUtil;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @Title:通知服务基类
* @Author:wangchenggong
* @Date 2020/9/27 13:41
* @Description
* @Version
*/
public abstract class AbstractNoticeService implements NoticeService<NoticeResult>{
/**
* 模拟数据字典中 不同系统的通知地址
*/
private static Map<String,String> noticeUrlMap = new HashMap<>();
static {
noticeUrlMap.put("aaa","http://xxx/aaa/");
noticeUrlMap.put("bbb","http://xxx/bbb/");
}
@Override
public NoticeResult notice(NoticeMessage noticeMessage) throws Exception {
NoticeResult noticeResult = null;
//1.获取通知地址(不同系统的通知地址一般可以配置在数据字典中,可以从数据字典中获取)
String noticeUrl = noticeUrlMap.get(noticeMessage.getSystemCode());
//2.构建通知内容
String noticeContent = this.buildNoticeContent(noticeMessage.getUniqueNo());
//3.发送通知内容
String result = HttpClientUtil.doPostJson(noticeUrl, noticeContent);
if(!StringUtils.isEmpty(result)){
try{
noticeResult = JSON.parseObject(result, NoticeResult.class);
}catch (Exception e){
noticeResult = new NoticeResult("9001", "The result of notice is empty!");
}
}else{
noticeResult = new NoticeResult("9001", "The noticeUrl is empty!");
}
//4.更新通知状态
this.updateNoticeStatus(noticeResult, noticeMessage);
return noticeResult;
}
/**
* 更新通知状态
* @param noticeResult 通知结果
* @param noticeMessage 通知的消息信息
*/
protected abstract void updateNoticeStatus(NoticeResult noticeResult, NoticeMessage noticeMessage);
/**
* 根据通知消息ID构建消息通知内容
* @param uniqueNo
* @return
*/
protected abstract String buildNoticeContent(String uniqueNo);
}
- 使用模板方法模式实现数据访问接口
import com.success.dataAccess.dao.BaseMapper;
import java.util.List;
/**
* @Title:数据操作服务基类(继承了BaseService中的泛型类型)
* @Author:wangchenggong
* @Date 2020/9/26 22:42
* @Description 增删改查的实现需要两步:1.获取对应的Mapper 2.调用Mapper对应的方法实现增删改查
* @Version
*/
public abstract class AbstractService<K,T> implements BaseService<K,T>{
protected abstract BaseMapper<K,T> getBaseMapper();
@Override
public T selectByPrimaryKey(K k){
return (T) getBaseMapper().selectByPrimaryKey(k);
}
@Override
public List<T> selectList(T t){
return getBaseMapper().selectList(t);
}
@Override
public int deleteByPrimaryKey(K k){
return getBaseMapper().deleteByPrimaryKey(k);
}
@Override
public int insert(T t){
return getBaseMapper().insert(t);
}
@Override
public int updateByPrimaryKeySelective(T t){
return getBaseMapper().updateByPrimaryKeySelective(t);
}
}
模版类已经实现了父接口中的抽象方法,子类只需要继承模板类,专注实现子接口中定义的抽象方法即可
import com.success.dataAccess.bean.AccountInfo;
import com.success.dataAccess.dao.BaseMapper;
import com.success.dataAccess.service.AbstractService;
import com.success.dataAccess.service.AccountInfoService;
import org.springframework.stereotype.Service;
/**
* @Title:账户信息服务接口实现类
* @Author:wangchenggong
* @Date 2020/9/27 10:18
* @Description
* @Version
*/
@Service
public class AccountInfoServiceImpl extends AbstractService<Long, AccountInfo> implements AccountInfoService {
//这里需要注入AccountInfoMapper
@Override
protected BaseMapper<Long, AccountInfo> getBaseMapper() {
return null;
}
}
- 使用模板方法模式实现redis分布式锁
// 获取账户锁
long lockId = rCustomerAccountLock.nextId();
String customerIdStr = String.valueOf(myCustomerId);
if (!rCustomerAccountLock.tryLock(customerIdStr, lockId, tryTimeout, TimeUnit.MILLISECONDS)) {
throw new KnowledgeException("该账户已经被锁定,请稍后");
}
try {
// 具体业务处理
...
} finally {
rCustomerAccountLock.unlock(customerIdStr, lockId);
}