01、什么是 Template ?
模版方法设计模式是一种行为型设计模式。官方定义理解比较隐晦,翻译成中文意思是这样的
模板方法模式在一个方法中定义一个 算法骨架,并将某些步骤推迟到 子类中实现。模板方法模式可以让子类在 不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里通过一个模版方法的UML类图来方便理解
通俗来讲 : 定义一个抽象类 AbstractTemplate
,并定义一个或若干抽象方法 abstractMethod
由子类去继承抽象类的同时实现抽象方法, 在抽象类的 operation
方法中调用抽象方法,最终调用的就是不同子类实现的方法逻辑
这也就达到了官方所说的在不改变方法逻辑的情况下,重新定义了算法中的某些步骤
operation
对应官方解释里的 算法骨架,abstractMethod
对应 某些步骤
读到这里不明白的朋友可以 细心看下UML类图,下面的实际应用也是根据此图进行的
02、为什么要用 Template
模版方法主要作用:复用性 和 扩展性
复用性想必大家 日常都有使用,可能不清楚这是模版方法的特性之一
2.1 复用性
核心思想就是 父级定义公共实现,由子级进行调取使用
举个例子大家就明白了,定义抽象发送消息,邮箱消息发送调取父类的 sendMessage
方法进行发送消息
这里使用 final
关键字就是不希望子类重写方法,实际应用根据场景来定义,有或没有都可以
public abstract class AbstractSendMessage {
public final void sendMessage(Message message) {
sendService.sendMessage(message);
}
}
class EmailMessage extends AbstractSendMessage {
public void sendEmailMessage(EmailMessage emailMessage) {
Message message = buildMessage(emailMessage);
super.sendMessage(message);
}
}
是不是挺容易理解的,这只是复用特性的其中一种,还有几种方式都是大同小异
今天重点不在复用性,而是另外一个特性
2.2 扩展性
扩展性指的就是文初所说的,在不修改方法逻辑的前提下,变更其中的某些步骤。这里通过一个项目中实际场景来说明
03、实际运用
老规矩,先来说一下项目中是根据什么场景进行实际运用的
由于最近要重构一部分业务,原因是因为 处理数据量过大,SO
目前的思路是进行 多线程并发执行,具体业务以及解决思路不详细说明了
由于线程在项目中属于重要且珍贵的资源,阿里巴巴开发规范中明确规定 线程资源必须交由线程池进行管理。这次实际场景也是围绕 模版设计模式的扩展性来构造线程池
构造线程池
首先定义构建线程池的抽象类,其中 initParam
交由需要创建线程池的客户端实现具体细节
@Slf4j
public abstract class AbstractTreadPoolTemplate {
// 构建初始化参数
public abstract ThreadPoolInitParam initParam();
// 构造线程池
public ExecutorService buildPool() {
ThreadPoolInitParam threadPoolInitParam = initParam();
ExecutorService executorService =
new ThreadPoolExecutor(threadPoolInitParam.corePoolNum,
threadPoolInitParam.maxPoolNum,
threadPoolInitParam.keepAliveTime,
threadPoolInitParam.timeUnit,
threadPoolInitParam.workQueue,
threadPoolInitParam.threadFactory,
threadPoolInitParam.rejectedExecutionHandler);
return executorService;
}
}
核心参数构建
由于阿里开发手册规范认为 JDK
推出 Executors
创建线程池的方法各有弊端
所以我们使用 JDK
原生的 ThreadPoolExecutor
来进行管理线程资源,但是核心参数需要根据 业务规模以及场景来定义
个人认为业务不到一定规模,正确使用
Executors
的话,并没有什么问题。这句话偷偷说,防止被怼 [笑哭]
ThreadPoolInitParam
提供有默认的参数,但是强烈推荐 根据业务量重载参数赋值方法
@Data
:Lombok
提供注解,涵盖了好几个注解,自行百度吧
@Accessors(chain = true)
级联操作,set属性的方法返回对象本身本来想使用
@RequiredArgsConstructor
,考虑到还得打备注,算了吧对
Lombok
不熟悉的朋友可以去点击公众号最新推送,里面有一篇介绍性文章涵盖了80%+的注解
@Data
@Accessors(chain = true)
public class ThreadPoolInitParam {
private Integer doubleCpu = Runtime.getRuntime().availableProcessors() << 1;
private Integer corePoolNum = doubleCpu + 1;
private Integer maxPoolNum = doubleCpu << 1;
private Long keepAliveTime = 0L;
private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
private BlockingQueue workQueue = new ArrayBlockingQueue(500);
private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
private ThreadFactory threadFactory;
private String threadName;
public ThreadPoolInitParam(String threadName, boolean isDaemon) {
this.threadFactory = new InitThreadFactory(threadName, isDaemon);
this.threadName = threadName;
}
}
客户端实现
客户端这里通过 new
抽象类来进行重写 initParam
方法,并填入 ThreadPoolInitParam
需要的参数后创建自定义线程池
细心的读者可以看到创建线程池尾部使用的 buildPool()
方法,是不是有一丝构建的意思呢
没错,这里使用到了 构建者设计模式,只不过没有在文中进行体现,会由后续文章来进行讲解
@Bean("dataIsuExecutor")
public ExecutorService dataIsuExecutor() {
return new AbstractTreadPoolTemplate() {
@Override
public ThreadPoolInitParam initParam() {
return new ThreadPoolInitParam("dataIsuExecutor", false)
.setCorePoolNum(80)
.setMaxPoolNum(100)
.setKeepAliveTime(60000L)
.setWorkQueue(new ArrayBlockingQueue(800));
}
}.buildPool();
}
04、总结
到这里,带有实际场景的 模版方法设计模式 就讲完了,模版方法模式还是比较简单容易理解的
不过,看的设计模式多了之后,发现很多模式之间 大同小异。这个时候我们就需要知道不同设计模式对应的业务场景是什么,在合适的场景使用合理的模式,才能设计出 "好代码"