介绍
模版方法设计模式是一种行为型设计模式,它允许你定义一个算法的框架,但将一些步骤的实现延迟到子类中。这样,子类可以重新定义算法的某些特定步骤,而不改变算法的结构。
模版方法包含以下主要角色:
抽象类(Abstract Class)
-
定义了一个提供了算法骨架的模版方法,包含了算法中的基本步骤和一些具体实现的方法。
-
可能包含一些抽象方法,这些方法由具体的子类来实现,以完成算法的特定步骤。
具体子类(Concrete Subclasses):
-
实现了抽象类中定义的抽象方法,以完成算法的特定步骤。
-
可以根据需要重写模版方法中的某些步骤,以实现特定的功能。
在模版方法设计模式中,抽象类和具体子类之间的关系是一种典型的模板(Template)与具体内容的关系。抽象类定义了算法的框架和骨架,而具体子类提供了具体的实现内容,从而形成了一个完整的算法。这种设计使得算法的框架和具体内容分离,提高了代码的可复用性和可扩展性。
主要优点包括:
-
代码复用性:
通过定义算法的框架和基本步骤,模版方法将相同的代码抽取到抽象类中,使得这部分代码可以在多个具体子类中被复用,从而减少了重复编码。 -
扩展性:
允许子类在不改变算法整体结构的情况下,重写或者扩展算法的特定步骤,以满足不同的需求,从而提高了代码的灵活性和可扩展性。 -
代码结构清晰:
模版方法将算法的框架和具体实现分离开来,使得代码结构更加清晰,易于维护和理解。 -
促进统一性:
通过定义一个统一的模版方法,可以确保所有的子类都按照相同的算法来执行操作,从而保持了系统中相关行为的统一性。
使用场景
- JDK中的IO类(如InputStream和OutputStream):JDK中的IO类库中的InputStream和OutputStream等类就是使用了模版方法设计模式。它们定义了读取和写入数据的基本算法框架,而具体的读取和写入操作则留给子类来实现,例如FileInputStream和FileOutputStream等。
- Servlet生命周期管理:Servlet生命周期管理是一个很好的模版方法设计模式的例子。Servlet规范定义了Servlet的生命周期,包括初始化、处理请求和销毁等阶段。Servlet容器(如Tomcat)提供了Servlet接口,开发者只需继承Servlet接口并重写特定方法来实现自己的Servlet类,而Servlet容器会根据规范来管理Servlet的生命周期。
-
Spring框架中的JdbcTemplate:在Spring框架中,JdbcTemplate是一个用于执行SQL查询和更新的模版类。它提供了执行SQL操作的基本算法框架,如创建连接、执行SQL语句和处理结果等,而具体的SQL语句和参数设置则由用户提供。
-
JUnit测试框架:JUnit测试框架也使用了模版方法设计模式。JUnit定义了一系列用于编写单元测试的基本方法,如setUp()、tearDown()和testXxx()等,用户只需继承TestCase类并重写这些方法来编写自己的测试用例。
-
电商网站订单处理流程:在电商网站中,订单处理流程通常具有一定的规范,如创建订单、支付订单、发货等。这些订单处理流程可以使用模版方法设计模式来实现,定义一个订单处理的抽象类,其中包含订单处理的基本步骤,而具体的订单处理流程则由不同的订单类型子类来实现,如普通订单、预定订单等。
-
银行业务流程:银行业务流程中,比如开户流程、贷款审批流程等,也可以使用模版方法设计模式来实现。银行业务流程中的各个环节有固定的操作步骤,但每种业务又有不同的具体实现方式,因此可以使用模版方法来定义通用的流程,而具体的业务流程则由不同的业务子类来实现。
示例
下面是一个简单的Java示例,展示了如何使用模版方法设计模式来实现一个通用的文档处理器,该处理器可以根据不同类型的文档(如Word文档和PDF文档)执行不同的操作。
// 抽象类:文档处理器
abstract class DocumentProcessor {
// 模版方法,定义了处理文档的算法框架
public final void processDocument() {
openDocument(); // 打开文档
if (isValidDocument()) { // 如果文档有效
parseDocument(); // 解析文档
extractText(); // 提取文本
processText(); // 处理文本
} else {
System.out.println("Invalid document type."); // 不支持的文档类型
}
closeDocument(); // 关闭文档
}
// 具体步骤的实现
private void openDocument() {
System.out.println("Opening document"); // 打开文档的具体实现
}
private void closeDocument() {
System.out.println("Closing document"); // 关闭文档的具体实现
}
// 抽象方法,由子类实现
abstract void parseDocument(); // 解析文档的抽象方法
abstract void extractText(); // 提取文本的抽象方法
abstract void processText(); // 处理文本的抽象方法
// 钩子方法,子类可以选择性地覆盖
boolean isValidDocument() {
return true; // 默认实现,假设文档都是有效的
}
}
// 具体子类:处理Word文档的文档处理器
class WordDocumentProcessor extends DocumentProcessor {
@Override
void parseDocument() {
System.out.println("Parsing Word document"); // 解析Word文档的具体实现
}
@Override
void extractText() {
System.out.println("Extracting text from Word document"); // 提取Word文档文本的具体实现
}
@Override
void processText() {
System.out.println("Processing text of Word document"); // 处理Word文档文本的具体实现
}
}
// 具体子类:处理PDF文档的文档处理器
class PdfDocumentProcessor extends DocumentProcessor {
@Override
void parseDocument() {
System.out.println("Parsing PDF document"); // 解析PDF文档的具体实现
}
@Override
void extractText() {
System.out.println("Extracting text from PDF document"); // 提取PDF文档文本的具体实现
}
@Override
void processText() {
System.out.println("Processing text of PDF document"); // 处理PDF文档文本的具体实现
}
@Override
boolean isValidDocument() {
// 额外的PDF格式验证逻辑
return checkPdfFormat(); // 验证PDF格式
}
private boolean checkPdfFormat() {
// 模拟验证PDF格式的逻辑
System.out.println("Checking PDF format"); // 验证PDF格式的具体实现
return true; // 假设验证通过
}
}
public class TemplateMethodDemo {
public static void main(String[] args) {
// 创建Word文档处理器
DocumentProcessor wordProcessor = new WordDocumentProcessor();
// 处理Word文档
wordProcessor.processDocument();
System.out.println();
// 创建PDF文档处理器
DocumentProcessor pdfProcessor = new PdfDocumentProcessor();
// 处理PDF文档
pdfProcessor.processDocument();
}
}
项目应用
背景:通信行业需要接入多家供应商,开放人员需要对接号卡、流量包等产品接口回调,虽然每个供应商都走自己的策略类,但是涉及订单状态回调的代码存在以下问题:每个回调策略类互相拷贝,存在大量重复代码和公共方法,可读性差,未对业务流程进行标准化,不利于后续维护和进一步拓展,为了解决以上问题和提高开发接入效率,采用模版方法进行回调策略的重构。
设计思路:采取策略模式+模板方法重构,抽取了公共流程和通用方法,允许子类可以重写钩子方法,灵活性高。原则上是一个产品类型一套模版,如无特殊则继承公共模板,核心关注doCallback。
抽象类(Abstract Class)
public interface SupplierCallback<R> {
/**
* 获取供应商策略
*
* @return 供应商策略
*/
SupplierStrategyNameEnum getSupplierStrategy();
/**
* 获取产品类型集合
*
*/
List<ProductBusinessTypeEnum> getProductTypeList();
/**
* 下单后主动回调,防止回调比下单快到情况
* @param orderInfo
*/
void callback(OrderInfo orderInfo);
/**
* 供应商回调订单状态变更
* @param orderNo
* @param callbackParam
*/
void callback(String orderNo, R callbackParam);
}
/**
* 通用回调抽象策略
*/
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
@Component
public abstract class AbstractCommonCallback<R extends CallbackParam> implements SupplierCallback<R> {
protected final OrderInfoService orderInfoService;
protected final JunboCallbackService junboCallbackService;
protected final FsNoticeService fsNoticeService;
protected final OrderFailTypeParseUtil orderFailTypeParseUtil;
protected final PromotionFacade promotionFacade;
protected final ProductService productService;
/**
* 解析回调参数
*
* @param responseMsg
* @return
*/
protected abstract R parseCallbackParam(String responseMsg);
/**
* 根据时间获取最新回调参数
*
* @param orderNo
* @return
*/
protected R getNewCallBack(String orderNo) {
// 获取最新回调时间
List<JunboCallback> list = junboCallbackService.lambdaQuery()
.eq(JunboCallback::getSysOrderId, orderNo)
.list();
if (CollUtil.isEmpty(list)) {
return null;
}
R callback = null;
DateTime createTime = null;
for (JunboCallback junboCallback : list) {
R r = parseCallbackParam(junboCallback.getResponseMsg());
if (callback == null) {
callback = r;
createTime = DateTime.of(junboCallback.getCreateTime());
} else {
// 回调时间字段为null,取创建时间
Date oldTime = Objects.isNull(callback.getCallbackTime()) ? createTime : callback.getCallbackTime();
Date newTime = Objects.isNull(r.getCallbackTime()) ? junboCallback.getCreateTime() : r.getCallbackTime();
if (newTime.after(oldTime)) {
callback = r;
createTime = DateTime.of(junboCallback.getCreateTime());
}
}
}
return callback;
}
@Override
public final void callback(OrderInfo orderInfo) {
Long id = orderInfo == null ? null : orderInfo.getId();
log.info("开始执行订单主动回调供应商策略:orderId={},strategy={}",id,getSupplierStrategy());
try {
if(orderInfo == null || StringUtils.isBlank(orderInfo.getOrderNo())) {
log.warn("异常订单数据,订单不存在或订单号为空,orderId={}",id);
return;
}
// 获取最新的回调参数
R callbackParam = getNewCallBack(orderInfo.getOrderNo());
if (callbackParam == null) {
return;
}
CallbackResult callbackResult = doCallback(orderInfo, callbackParam);
if(callbackResult == null || callbackResult.getOrderInfo() == null) {
log.warn("回调更新结果为空");
return;
}
afterCallBack(orderInfo, callbackResult);
} catch (Exception e) {
log.error("供应商回调执行失败,orderId={}",id,e);
}
}
@Override
public final void callback(String orderNo, R callbackParam) {
log.info("开始执行供应商策略回调:orderNo={},strategy={}",orderNo,getSupplierStrategy());
try {
if (StringUtils.isBlank(orderNo)) {
log.warn("异常订单数据,订单号为空:{}", callbackParam);
return;
}
OrderInfo orderInfo = orderInfoService.getByOrderNo(orderNo);
if (orderInfo == null) {
return;
}
if (callbackParam.getCallbackTime() != null) {
Date time = callbackParam.getCallbackTime();
R callback = getNewCallBack(orderNo);
Date newTime = callback.getCallbackTime();
if (newTime.after(time)) {
log.warn("回调时间小于最新回调时间,不处理,{}", callbackParam);
return;
}
}
CallbackResult callbackResult = doCallback(orderInfo, callbackParam);
if(callbackResult == null || callbackResult.getOrderInfo() == null) {
log.warn("回调更新结果为空");
return;
}
afterCallBack(orderInfo, callbackResult);
} catch (Exception e) {
log.error("供应商回调执行失败,orderNo:{},callbackParam:{}", orderNo, callbackParam, e);
}
}
/**
* 回调业务逻辑实现
*
* @param orderInfo
* @param callbackParam
* @return
*/
protected abstract CallbackResult doCallback(OrderInfo orderInfo, R callbackParam);
protected void afterCallBack(OrderInfo orderInfo, CallbackResult callbackResult) {
// 订单状态变更记录;
orderInfoService.saveOrderStatusChangeRecordByOrderInfo(callbackResult.getOrderInfo(), orderInfo.getProductType());
if(!Objects.equals(orderInfo.getOrderStatus(),OrderStatusEnum.SUCCEED.getValue()) && Objects.equals(callbackResult.getOrderInfo().getOrderStatus(),OrderStatusEnum.SUCCEED.getValue())) {
// 更新成功
orderInfoService.updateProductSuccessOrders(orderInfo.getProductCode(), 1);
} else if(Objects.equals(orderInfo.getOrderStatus(),OrderStatusEnum.SUCCEED.getValue()) && Objects.equals(callbackResult.getOrderInfo().getOrderStatus(),OrderStatusEnum.FAILED.getValue())) {
// 更新失败
orderInfoService.updateProductSuccessOrders(orderInfo.getProductCode(), -1);
}
}
/**
* @param param 回调参数
* @param orderInfo 订单信息
* @param update 更新订单
* @description 处理成功->失败订单
* @author youmu
* @date 2023/8/29 18:36
*/
protected void handleSuccessToFail(R param, OrderInfo orderInfo, OrderInfo update) {
if (OrderStatusEnum.SUCCEED.getValue().equals(orderInfo.getOrderStatus())) {
promotionFacade.updateOrderSuccessCount(orderInfo, -1);
update.setOrderStatus(OrderStatusEnum.FAILED.getValue());
if (StringUtils.isNotBlank(param.getErrMsg())) {
update.setFailCode(orderFailTypeParseUtil.matchFailType(orderInfo.getSupplierId(), orderInfo.getProductType(),param.getErrMsg()).getCode());
update.setExecuteResultMsg(param.getErrMsg());
}
// 订单信息日志
reportLog(orderInfo, update, false);
// 状态变更通知
fsNoticeService.orderStatusNotice(orderInfo, param.getErrMsg(), OrderStatusEnum.SUCCEED, OrderStatusEnum.FAILED);
}
}
/**
* @param param 回调参数
* @param orderInfo 订单信息
* @param update 更新订单
* @description 处理失败->成功订单
* @author youmu
* @date 2023/8/29 18:36
*/
protected void handleFailToSuccess(R param, OrderInfo orderInfo, OrderInfo update) {
if (OrderStatusEnum.FAILED.getValue().equals(orderInfo.getOrderStatus())) {
promotionFacade.updateOrderSuccessCount(orderInfo, 1);
update.setOrderStatus(OrderStatusEnum.SUCCEED.getValue());
// 订单信息日志
reportLog(orderInfo, update, true);
fsNoticeService.orderStatusNotice(orderInfo, "", OrderStatusEnum.FAILED, OrderStatusEnum.SUCCEED);
// 清空失败原因的code
update.setFailCode(0);
update.setExecuteResultMsg("");
}
}
}
/**
* 号卡回调抽象策略
*/
@Slf4j
@Component
public abstract class AbstractCardCallback<R extends CallbackParam> extends AbstractCommonCallback<R> {
private final SalvageOrderLogService salvageOrderLogService;
public AbstractCardCallback(OrderInfoService orderInfoService, JunboCallbackService junboCallbackService, SalvageOrderLogService salvageOrderLogService, FsNoticeService fsNoticeService, OrderFailTypeParseUtil orderFailTypeParseUtil, PromotionFacade promotionFacade, ProductService productService) {
super(orderInfoService, junboCallbackService, fsNoticeService, orderFailTypeParseUtil, promotionFacade, productService);
this.salvageOrderLogService = salvageOrderLogService;
}
@Override
public List<ProductBusinessTypeEnum> getProductTypeList() {
return Collections.singletonList(ProductBusinessTypeEnum.HK);
}
protected void afterCallBack(OrderInfo orderInfo, CallbackResult callbackResult) {
// 回调后置处理逻辑
}
}
具体子类(Concrete Subclasses)
/**
* 众联回调策略接口
*/
@Slf4j
@Service
public class ZoneLianCardCallback extends AbstractCardCallback<ZoneLianCardOrderCallBackParam> {
public ZoneLianCardCallback(OrderInfoService orderInfoService, JunboCallbackService junboCallbackService, SalvageOrderLogService salvageOrderLogService, FsNoticeService fsNoticeService, OrderFailTypeParseUtil orderFailTypeParseUtil, PromotionFacade promotionFacade, ProductService productService) {
super(orderInfoService, junboCallbackService, salvageOrderLogService, fsNoticeService, orderFailTypeParseUtil, promotionFacade, productService);
}
@Override
public SupplierStrategyNameEnum getSupplierStrategy() {
return SupplierStrategyNameEnum.ZL;
}
@Override
protected ZoneLianCardOrderCallBackParam parseCallbackParam(String responseMsg) {
return JacksonUtils.parseObject(responseMsg, ZoneLianCardOrderCallBackParam.class);
}
@Override
protected CallbackResult doCallback(OrderInfo orderInfo, ZoneLianCardOrderCallBackParam param) {
// 具体业务逻辑
}
}
/**
* 风茂存量回调策略接口
*/
@Slf4j
@Service
public class FengmaoStockCallback extends AbstractStockCallback<FengmaoOrderCallbackParam> {
public FengmaoStockCallback(OrderInfoService orderInfoService, JunboCallbackService junboCallbackService, FsNoticeService fsNoticeService, OrderFailTypeParseUtil orderFailTypeParseUtil, PromotionFacade promotionFacade, ProductService productService) {
super(orderInfoService, junboCallbackService, fsNoticeService, orderFailTypeParseUtil, promotionFacade, productService);
}
@Override
public SupplierStrategyNameEnum getSupplierStrategy() {
return SupplierStrategyNameEnum.FENGMAO;
}
@Override
protected FengmaoOrderCallbackParam parseCallbackParam(String responseMsg) {
return JacksonUtils.parseObject(responseMsg, FengmaoOrderCallbackParam.class);
}
@Override
protected CallbackResult doCallback(OrderInfo orderInfo, FengmaoOrderCallbackParam param) {
// 具体业务逻辑
}
}
工厂调用
package com.onecode.tc.platform.service.supplier.callback;
import com.google.common.collect.Maps;
import com.onecode.tc.platform.enums.BusinessTypeEnum;
import com.onecode.tc.platform.enums.ProductBusinessTypeEnum;
import com.onecode.tc.platform.enums.SupplierStrategyNameEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 供应商回调策略工厂类
*/
@Slf4j
@Component
public class SupplierCallbackFactory<R> {
private final Map<String, SupplierCallback<R>> strategyMap = Maps.newHashMap();
private static final Map<BusinessTypeEnum,List<ProductBusinessTypeEnum>> businessTypeMap;
// 静态初始化块
static {
businessTypeMap = Maps.newHashMap();
// 添加初始化的键值对
businessTypeMap.put(BusinessTypeEnum.CARD, Collections.singletonList(ProductBusinessTypeEnum.HK));
businessTypeMap.put(BusinessTypeEnum.STOCK, Arrays.asList(ProductBusinessTypeEnum.LLB,ProductBusinessTypeEnum.CL));
}
@Autowired
public SupplierCallbackFactory(List<SupplierCallback<R>> strategies) {
for (SupplierCallback<R> strategy : strategies) {
for(ProductBusinessTypeEnum productBusinessTypeEnum : strategy.getProductTypeList()) {
strategyMap.put(strategy.getSupplierStrategy().getDesc() + "_" + productBusinessTypeEnum.getValue(), strategy);
}
}
}
public SupplierCallback<R> getStrategy(String supplierStrategy, Integer productType) {
SupplierCallback<R> supplierCallbackStrategy = getSupplierCallback(supplierStrategy, productType);
if(supplierCallbackStrategy == null) {
log.warn("找不到对应供应商回调策略类,supplierStrategy={},productType={}", supplierStrategy,productType);
}
return supplierCallbackStrategy;
}
private SupplierCallback<R> getSupplierCallback(String supplierStrategy, Integer productType) {
return strategyMap.get(supplierStrategy + "_" + productType);
}
public SupplierCallback<R> getStrategy(SupplierStrategyNameEnum supplierStrategy, ProductBusinessTypeEnum productType) {
SupplierCallback<R> supplierCallbackStrategy = getSupplierCallback(supplierStrategy.getDesc(), productType.getValue());
if(supplierCallbackStrategy == null) {
log.warn("找不到对应供应商回调策略类,supplierStrategy={},productType={}", supplierStrategy,productType);
}
return supplierCallbackStrategy;
}
public SupplierCallback<R> getStrategy(SupplierStrategyNameEnum supplierStrategy, BusinessTypeEnum businessTypeEnum) {
for(ProductBusinessTypeEnum productBusinessTypeEnum : businessTypeMap.get(businessTypeEnum)) {
SupplierCallback<R> supplierCallback = getSupplierCallback(supplierStrategy.getDesc(), productBusinessTypeEnum.getValue());
if(supplierCallback != null) {
return supplierCallback;
}
}
log.warn("找不到对应供应商回调策略类,supplierStrategy={},businessType={}", supplierStrategy,businessTypeEnum);
return null;
}
}
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Tag(name = "杭州风茂回调")
@RestController
@RequestMapping("/open/api/callback/fengmao")
@Slf4j
public class FengmaoCallbackController {
private final SupplierCallbackFactory<CallbackParam> supplierCallbackFactory;
private final JunboCallbackService junboCallbackService;
@GetMapping("/order")
@Operation(summary = "订单回调接口")
public String cardOrderCallback(FengmaoOrderCallbackParam param) {
log.info("【杭州风茂回调】回调参数:{}", JacksonUtils.toJsonString(param));
//记录信息
JunboCallback junboCallback = new JunboCallback();
junboCallback.setSysOrderId(param.getLinkid());
junboCallback.setOrderStatus(param.getStatus());
junboCallback.setResponseMsg(JacksonUtils.toJsonString(param));
junboCallback.setOrderCode(param.getLinkid());
junboCallback.setContactNumber(param.getMobile());
junboCallback.setRemark(param.getMsg());
junboCallback.setSupplier(SupplierStrategyNameEnum.FENGMAO.getValue());
junboCallbackService.saveCallbackInfo(junboCallback);
supplierCallbackFactory.getStrategy(SupplierStrategyNameEnum.FENGMAO, BusinessTypeEnum.STOCK).callback(param.getLinkid(), param);
return "OK";
}
}