#策略模式+ 模板方法模式实战
一、缘起
最近在开发的一个系统中有这么一个场景。在订单到达以后需要进行签收,而签收根据提货方式的不同被分成了不同的模块。比如有自提的、有直送的或者需要配送的。在开发的过程中,本小汪童鞋发现,他们搜索条件好像除了提货方式并没有太多差异,并且签收的逻辑也是一样的。于是乎,小汪童鞋就在想能不能复用勒,为什么要复用呢?经常听说优秀的程序员要让代码更加简洁、职责单一、提升复用性等等。当然根本原因还是俺懒,真不想多写一行代码!!!!所以怎么复用呢?一开始想的是封装一个通用接口,根据前端传参从而给查询条件赋值。但是转念一想,这么简单的封装差点b格啊。而且当前仅仅是查询,那万一以后衍生出其他的不同变故,那这个可扩展性不是跟翔一样。于是乎,脑子中想起一位故人经常说的四个字:设!计!模!式!。ok,经过一番调研,选择使用策略模式+模板方式模式实现。
二、介绍
1、策略模式
介绍
策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
解决问题
解决在多种相似算法存在时,使用条件语句(如if…else)导致的复杂性和难以维护的问题。
使用场景
当一个系统中有许多类,它们之间的区别仅在于它们的行为时。
实现方式
- 定义策略接口:所有策略类都将实现这个统一的接口。
- 创建具体策略类:每个策略类封装一个具体的算法或行为。
- 上下文类:包含一个策略对象的引用,并通过该引用调用策略。
策略模式包含核心角色:
- 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
- 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
- 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。
策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。
图示

实现示例:
步骤一 :创建一个接口。
## Strategy.java
public interface Strategy { public int doOperation(int num1, int num2); }
步骤二:创建实现接口的实体类。
## OperationAdd.java
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
## OperationSubtract.java
public class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
## OperationMultiply.java
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
**步骤三:**创建 Context 类。
## Context.java
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
**步骤四:**使用 Context 来查看当它改变策略 Strategy 时的行为变化。
## StrategyPatternDemo.java
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
**步骤五:**使用 Context 来查看当它改变策略 Strategy 时的行为变化。
## 执行程序,输出结果:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
2、模板方法模式
介绍
模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
解决问题
解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复。
使用场景
当存在一些通用的方法,可以在多个子类中共用时。
实现方式
- 定义抽象父类:包含模板方法和一些抽象方法或具体方法。
- 实现子类:继承抽象父类并实现抽象方法,不改变算法结构。
模板方法模式包含核心角色
- 抽象父类(Abstract Class):
- 定义了模板方法和一些抽象方法或具体方法。
- 具体子类(Concrete Classes):
- 继承自抽象父类,并实现抽象方法。
- 钩子方法(Hook Method)(可选):
- 在抽象父类中定义,可以被子类重写,以影响模板方法的行为。
- 客户端(Client)(可选):
- 使用抽象父类和具体子类,无需关心模板方法的细节。
图示

实现示例:
步骤一: 创建一个抽象类,它的模板方法被设置为 final。
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
**步骤二:**创建扩展了上述类的实体类。
public class Cricket extends Game {
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
}
public class Football extends Game {
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
}
**步骤三:**使用 Game 的模板方法 play() 来演示游戏的定义方式。
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
**步骤四:**执行
## 执行程序,输出结果:
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!
三、实战
介绍
查询条件除了提货方式,其他参数都一样。而签收流程虽然现在一样,但是为了保证之后可能的业务差异提供易扩展的接口。
实现
步骤一:为了保证类型安全先创建枚举类
public enum GetWayType {
SELF_PICKUP(0, "自提"),
DIRECT(1, "直送"),
DELIVERY(2, "配送");
private final Integer code;
private final String label;
GetWayType(Integer code, String label) {
this.code = code;
this.label = label;
}
public Integer getCode() {
return code;
}
// 根据code获取枚举
public static GetWayType fromCode(String code) {
return Arrays.stream(values())
.filter(type -> type.code.equals(code))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("无效的提货方式"));
}
}
步骤二:公共查询条件封装
@Data
public class ReceiptQueryParam {
@Schema(description="条件1")
private String A;
@Schema(description="条件2")
private String B;
@Schema(description="条件3")
List<String> C;
@Schema(description="条件4")
private String D;
@Schema(description="条件5")
private String E;
@Schema(description="条件6")
private String F;
@Schema(description="条件7")
private Integer G;
@Schema(description="条件8")
private String H;
private GetWayType I; // 关键差异点
}
步骤三: 实体类设计
@Data
@TableName("t_receipt")
public class Receipt {
@TableId(type = IdType.AUTO)
private Long id;
// 所有类型公共字段
private String orderNo;
private Date createTime;
private Date signTime;
private String signPerson;
@TableField("get_way")
private GetWayType GetWayType; // 提货方式
// 自提特有字段
private String pickupPerson;
private String idCard;
// 直达特有字段
private String driverName;
private String vehicleNo;
// 配送特有字段
private String courier;
private String courierPhone;
}
**步骤四:**Mapper定义
public interface ReceiptMapper extends BaseMapper<Receipt> {
// 自定义复杂查询可以在这里定义
}
**步骤五:**Service层实现
- 使用MyBatis-Plus动态查询
@Service
@RequiredArgsConstructor
public class ReceiptQueryService {
private final ReceiptMapper receiptMapper;
public Page<Receipt> queryReceipts(ReceiptQueryParam param, Page<Receipt> page) {
QueryWrapper<Receipt> wrapper = new QueryWrapper<>();
// 公共条件
wrapper.eq(StringUtils.isNotBlank(param.getOrderNo()), "order_no", param.getOrderNo());
if (param.getStartDate() != null && param.getEndDate() != null) {
wrapper.between("sign_time", param.getStartDate(), param.getEndDate());
} else if (param.getStartDate() != null) {
wrapper.ge("sign_time", param.getStartDate());
} else if (param.getEndDate() != null) {
wrapper.le("sign_time", param.getEndDate());
}
// 提货方式条件
if (param.getDeliveryType() != null) {
wrapper.eq("get_type", param.getGetWay().getCode());
}
return receiptMapper.selectPage(page, wrapper);
}
}
2、签收服务(策略模式+模板方法)
// 签收策略接口
public interface SignStrategy {
void executeSign(SignRequest request);
}
// 策略模板
public abstract class AbstractSignStrategy implements SignStrategy {
protected final ReceiptMapper receiptMapper;
public AbstractSignStrategy(ReceiptMapper receiptMapper) {
this.receiptMapper = receiptMapper;
}
@Override
public void executeSign(SignRequest request) {
validate(request);
doSign(request);
postSignProcess(request);
}
protected void validate(SignRequest request) {
// 公共校验逻辑
if (request.getSignTime() == null) {
throw new ValidationException("签收时间不能为空");
}
Receipt receipt = receiptMapper.selectOne(new QueryWrapper<Receipt>()
.eq("order_no", request.getOrderNo()));
if (receipt == null) {
throw new ValidationException("订单不存在");
}
if (receipt.getSignTime() != null) {
throw new ValidationException("订单已签收");
}
}
protected abstract void doSign(SignRequest request);
protected void postSignProcess(SignRequest request) {
// 公共后处理
logService.logSignOperation(request);
// 更新物流状态
logisticsService.updateStatus(request.getOrderNo(), "SIGNED");
}
// 公共签收操作
protected void commonSignOperation(SignRequest request) {
Receipt receipt = new Receipt();
receipt.setSignTime(request.getSignTime());
receipt.setSignPerson(request.getSignPerson());
receiptMapper.update(receipt, new UpdateWrapper<Receipt>()
.eq("order_no", request.getOrderNo()));
}
}
// 策略实现(当前逻辑相同)
@Service
public class SelfPickupStrategy extends AbstractSignStrategy {
public SelfPickupStrategy(ReceiptMapper receiptMapper) {
super(receiptMapper);
}
@Override
protected void doSign(SignRequest request) {
// 使用公共签收逻辑
commonSignOperation(request);
// 扩展点:自提特有逻辑
if (request.getPickupPerson() != null) {
Receipt receipt = new Receipt();
receipt.setPickupPerson(request.getPickupPerson());
receiptMapper.update(receipt, new UpdateWrapper<Receipt>()
.eq("order_no", request.getOrderNo()));
}
}
}
@Service
public class DirectStrategy extends AbstractSignStrategy {
public DirectStrategy(ReceiptMapper receiptMapper) {
super(receiptMapper);
}
@Override
protected void doSign(SignRequest request) {
commonSignOperation(request);
// 扩展点:直达特有逻辑
if (request.getDriverName() != null) {
Receipt receipt = new Receipt();
receipt.setDriverName(request.getDriverName());
receiptMapper.update(receipt, new UpdateWrapper<Receipt>()
.eq("order_no", request.getOrderNo()));
}
}
}
// 策略上下文
@Service
public class SignStrategyContext {
private final Map<String, SignStrategy> strategyMap = new ConcurrentHashMap<>();
@Autowired
public SignStrategyContext(List<SignStrategy> strategies) {
for (SignStrategy strategy : strategies) {
if (strategy instanceof SelfPickupStrategy) {
strategyMap.put(GetWayType.SELF_PICKUP.getCode(), strategy);
} else if (strategy instanceof DirectStrategy) {
strategyMap.put(GetWayType.DIRECT.getCode(), strategy);
} else if (strategy instanceof DeliveryStrategy) {
strategyMap.put(GetWayType.DELIVERY.getCode(), strategy);
}
}
}
public SignStrategy getStrategy(String getWayType) {
return Optional.ofNullable(strategyMap.get(getWayType))
.orElseThrow(() -> new ServiceException("不支持的提货方式"));
}
}
// 签收服务
@Service
@RequiredArgsConstructor
public class ReceiptSignService {
private final SignStrategyContext strategyContext;
public void signReceipt(String getWayType, SignRequest request) {
SignStrategy strategy = strategyContext.getStrategy(getWayType);
strategy.executeSign(request);
}
}
**步骤六:**DTO设计(继承体系)
@Data
public class SignRequest {
@NotBlank
private String orderNo;
@NotNull
private Date signTime;
@NotBlank
private String signPerson;
}
@Data
@EqualsAndHashCode(callSuper = true)
public class SelfPickupSignRequest extends SignRequest {
private String pickupPerson; // 自提特有字段
private String idCard;
}
@Data
@EqualsAndHashCode(callSuper = true)
public class DirectSignRequest extends SignRequest {
private String driverName; // 直达特有字段
private String vehicleNo;
}
**步骤七:**Controller层
@RestController
@RequestMapping("/receipts")
@RequiredArgsConstructor
public class ReceiptController {
private final ReceiptQueryService queryService;
private final ReceiptSignService signService;
// 查询接口
@GetMapping
public ResponseEntity<Page<Receipt>> queryReceipts(
@Valid ReceiptQueryParam param,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
Page<Receipt> page = new Page<>(pageNum, pageSize);
return ResponseEntity.ok(queryService.queryReceipts(param, page));
}
// 统一签收接口
@PostMapping("/sign/{deliveryType}")
public ResponseEntity<Void> signReceipt(
@PathVariable String deliveryType,
@Valid @RequestBody SignRequest request) {
signService.signReceipt(deliveryType, request);
return ResponseEntity.ok().build();
}
// 自提签收(扩展示例)
@PostMapping("/sign/self-pickup")
public ResponseEntity<Void> signSelfPickup(
@Valid @RequestBody SelfPickupSignRequest request) {
signService.signReceipt(DeliveryType.SELF_PICKUP.getCode(), request);
return ResponseEntity.ok().build();
}
}
**步骤八:**扩展新类型示例
// 1. 添加枚举
public enum DeliveryType {
// ...其他类型
EXPRESS("express", "快递签收");
}
// 2. 创建策略
@Service
public class ExpressStrategy extends AbstractSignStrategy {
public ExpressStrategy(ReceiptMapper receiptMapper) {
super(receiptMapper);
}
@Override
protected void doSign(SignRequest request) {
commonSignOperation(request);
// 快递特有逻辑
ExpressSignRequest expressReq = (ExpressSignRequest) request;
Receipt receipt = new Receipt();
receipt.setCourier(expressReq.getCourier());
receipt.setCourierPhone(expressReq.getCourierPhone());
receiptMapper.update(receipt, new UpdateWrapper<Receipt>()
.eq("order_no", request.getOrderNo()));
}
}
// 3. 在策略上下文中注册
// 修改SignStrategyContext构造方法
if (strategy instanceof ExpressStrategy) {
strategyMap.put(DeliveryType.EXPRESS.getCode(), strategy);
}
// 4. 添加DTO
@Data
@EqualsAndHashCode(callSuper = true)
public class ExpressSignRequest extends SignRequest {
private String courier;
private String courierPhone;
}
// 5. 添加Controller
@PostMapping("/sign/express")
public ResponseEntity<Void> signExpress(
@Valid @RequestBody ExpressSignRequest request) {
signService.signReceipt(DeliveryType.EXPRESS.getCode(), request);
return ResponseEntity.ok().build();
}
四、结论
跟之前的文章不同,这篇含大片代码。但是需要实践的东西代码自然是不能少的。虽然这样实现初次要写很多代码,但是极大的提升了可复用性和后续的可扩展性,总体也是利大于弊把!另外,本小汪童鞋也是现学现卖,如果有问题还希望各位大佬指正!!!!
更多推荐

所有评论(0)