模板方法
定义:
- 模板方法(Template Method)模式:定义一个操作中的算法的骨架,而将一些步骤延 迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 这里的算法的结构,可以理解为你根据需求设计出来的业务流程。特定的步骤就是指那些可能在 内容上存在变数的环节。
可以看出来,模板方法模式也是为了巧妙解决变化对系统带来的影响而设计的。使用模
板方法使系统扩展性增强,最小化了变化对系统的影响
日常生活中的模板方法:
- 造房时,地基、走线、水管都一样,只有在建筑后期装修上才有差异
- 西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
- 对于汽车,车从发动到停车的顺序是相同的,不同的是引擎声、鸣笛声等
- spring 中对 MYbatis的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
组成:
代码结构展示:
public class Main {
public static void main(String[] args) {
F f = new C1();
f.m();
}
}
abstract class F {
public void m() {
op1();
op2();
}
abstract void op1();
abstract void op2();
}
class C1 extends F {
@Override
void op1() {
System.out.println("op1");
}
@Override
void op2() {
System.out.println("op2");
}
}
- 模板方法m(),定义好了业务流程先后顺序,是先执行op1(),后执行op2().
- 至于具体的op1,op2实现逻辑和方式,可以留给子类去重写.
案例1
需求:计算加减乘除,把这四项基本运算,抽象成一个模板方法.
分析算法步骤:
- 分离需要运算的参数
- 对分离出来的运算,做相对应的计算
抽象计算类: AbstractCalculator
具体的计算,留给子类扩展,但算法流程步骤已经固定好
public abstract class AbstractCalculator {
/*主方法,实现对本类其它方法的调用*/
public final int calculate(String exp,String opt){
int array[] = split(exp,opt);
return calculate(array[0],array[1]);
}
//提取参数
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
/*被子类重写的方法*/
abstract public int calculate(int num1,int num2);
}
具体加法类: Plus
public class Plus extends AbstractCalculator{
@Override
public int calculate(int num1,int num2) {
return num1 + num2;
}
}
测试:StrategyTest
public class StrategyTest {
public static void main(String[] args) {
String exp = "8+8";
AbstractCalculator cal = new Plus();
int result = cal.calculate(exp, "\\+");
System.out.println("8+8= "+result);
}
}
案例 2 :读数据库表查询
读数据库表,获取jdbc连接,然后select读取表中数据,这些共性操作,完全也可以封装这些相同的步骤,到抽象类,做成模板方法,子类去继承就好.
步骤:
- 解析参数 (包括表名,select语句,mapper文件位置)
- 获取jdbc连接
- 拼接查询语句
- 查询出来的结果分装到map
- 抽象方法,留给子类去处理查询出来的结果集
思路 :
- 以上 步骤1 到步骤4 都可以封装到抽象类的openCursor()方法里面,
- 然后子类main只需要调用this.openCursor()就可以完成以上数据库查询操作
总结
应用场景:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;
- 将相同处理逻辑的代码放到抽象父类中,避免代码重复;提高代码的复用性。
- 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台, 符合开闭原则。
优点:
- 提高代码复用性
将相同部分的代码放在抽象的父类中 - 提高了拓展性
将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制 & 符合“开闭原则”
缺点:
-
类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
-
类数量的增加,间接地增加了系统实现的复杂度。
-
继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
注意事项:
- 为防恶意操作,一般模板方法都加上final关键字
案例3 线程池
Executor–ExecutorService–AbstractExecutorService-ThreadPoolExecutor
ThreadPoolExecutor他的父类是从AbstractExecutorService,而AbstractExecutorService的父类是ExecutorService,再ExecutorService的父类是Executo,
Executor–顶级接口
- Executor看它的名字也能理解,执行者,所以他有一个方法叫执行,void execute(Runnable command);那么执行的东西是Runnable,所以这个Executor有了之后呢由于它是一个接口,他可以有好多实现,因此我们说,有了Executor之后呢,我们现场就是一个任务的定义,
- 比如Runnable起了一个命令的意思,他的定义和运行就可以分开了,不像我们以前定义一个Thread,new一个Thread然后去重写它的Run方法.start才可以运行,或者以前就是你写了一个Runnable你也必须得new一个Thread出来,以前的这种定义和运行是固定的,是写死的就是你new一个Thread让他出来运行。有的同学他还是new一个Thread但是他有了各种各样新的玩法,不用你亲自去指定每一个Thread,他的运行的方式你可以自己去定义了,所以至于是怎么去定义的就看你怎么实现Executor的接口了,这里是定义和运行分开这么一个含义,所以这个接口体现的是这个意思,所以这个接口就比较简单,至于你是直接调用run还是new一个Thread那是你自己的事儿。
ExecutorService—接口
ExecutorService又是什么意思呢,他是从Executor继承,另外,他除了去实现Executor可以去执行一个任务之外,他还完善了整个任务执行器的一个生命周期,就拿线程池来举例子,一个线程池里面一堆的线程就是一堆的工人,执行完一个任务之后我这个线程怎么结束啊,线程池定义了这样一些个方法:
void shutdown();//结束
List<Runnable> shutdownNow();//马上结束
boolean isShutdown();//是否结束了
boolean isTerminated();//是不是整体都执行完了
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;//等着结束,等多长时间,时间到了还不结束的话他就返回false
-所以这里面呢,他是实现了一些个线程的线程池的生命周期的东西,扩展了Executor的接口,真正的线程池的现实是在ExecutorService的这个基础上来实现的。