这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
什么是模板方法模式?
一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。模板方法使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤(通用代码在抽象类实现,其他步骤在子类实现)。接下来,将以我工作中用到的场景举例。
背景
我们有一个模型训练平台,当我们训练模型的时候,需要预处理,先检测语料是否满足需要,如果满足发送模型训练消息到训练平台,生成训练任务ID返回。如果不满足要求,我们直接返回训练失败。
代码示例
1.定义任务执行接口
public interface BaseTrainService<K, V> {
V execute(K vo);
}
2.训练任务模板抽象类-实现通用模板
@Slf4j
public abstract class CommonTrainService<K, V> implements BaseTrainService<K, V> {
// 预处理
protected abstract boolean preHandle(K vo);
// 核心处理流程
protected abstract V handle(K vo);
// 后处理
protected abstract V postHandle(K vo, V dto);
@Override
public final V execute(K vo) {
V dto = null;
try {
boolean flag = preHandle(vo);
if (flag) {
dto = handle(vo);
}
dto = postHandle(vo, dto);
} catch (Exception e) {
log.error("处理训练任务异常", e);
}
return dto;
}
}
3.训练任务
简单训练任务-不需要预处理,直接发送训练消息
@Slf4j
public class SimpleTrainService extends CommonTrainService<TrainVO, Response<TrainDTO>> {
@Override
protected boolean preHandle(TrainVO vo) {
return true;
}
@Override
protected Response<TrainDTO> handle(TrainVO vo) {
TrainDTO result = new TrainDTO();
...
return Response.success(result);
}
@Override
protected Response<TrainDTO> postHandle(TrainVO vo, Response<TrainDTO> dto) {
if (dto == null) {
return Response.fail("创建训练任务失败");
}
return dto;
}
}
复杂训练任务-预处理判断是否发送训练消息
@Slf4j
public class ComplexTrainService extends CommonTrainService<TrainVO, Response<TrainDTO>> {
@Override
protected boolean preHandle(TrainVO vo) {
List<TrainRecord> list = ....
return CollectionUtils.isEmpty(list);
}
@Override
protected Response<TrainDTO> handle(TrainVO vo) {
TrainDTO result = new TrainDTO();
...
return Response.success(result);
}
@Override
protected Response<TrainDTO> postHandle(TrainVO vo, Response<TrainDTO> dto) {
if (dto == null) {
return Response.fail("创建训练任务失败");
}
return dto;
}
}
4. 客户端-调用具体类型训练任务
public class Client {
public Response<TrainDTO> simpleTrainTask(TrainVO vo){
ComplexTrainService service = new ComplexTrainService();
return service.execute(vo);
}
}
总结
优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景:
1、有多个子类共有的方法,且逻辑相同。
2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:
为防止恶意操作,一般模板方法都加上 final 关键词。