模板方法模式(Template Method Pattern)
定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。——《HeadFirst设计模式》
通俗地说,就是在超类中定义一系列方法,其中一个方法(骨架方法)顺序调用其它方法,子类则按需要重写部分方法(所谓的延迟到子类)
模式总结过程
公司要做一个电商系统中的货物入库模块,有四个步骤
- 接收货物信息
- 拼接请求报文
- 调用入库接口
- 记录日志
假设现在有三类货物,电器、衣服、家具
- 初步的代码如下
// 电器
public class Electric {
// 算法骨架
public void main() {
step1();
step2();
step3();
step4();
}
public void step1() {
System.out.println("接收电器信息");
}
public void step2() {
System.out.println("拼接电器请求报文");
}
public void step3() {
System.out.println("调用入库接口");
}
public void step4() {
System.out.println("记录日志");
}
}
// 衣服
public class Clothes {
// 算法骨架
public void main() {
step1();
step2();
step3();
step4();
}
public void step1() {
System.out.println("接收衣服信息");
}
public void step2() {
System.out.println("拼接衣服请求报文");
}
public void step3() {
System.out.println("调用入库接口");
}
public void step4() {
System.out.println("记录日志");
}
}
// 家具
public class Furniture {
// 算法骨架
public void main() {
step1();
step2();
step3();
step4();
}
public void step1() {
System.out.println("接收家具信息");
}
public void step2() {
System.out.println("拼接家具请求报文");
}
public void step3() {
System.out.println("调用入库接口");
}
public void step4() {
System.out.println("记录日志");
}
}
- 其中1、2是每类货物都不一样的,而3、4是一样的
- 货物属性不一样,货物信息肯定不一样,那请求的接口报文也就不一样、
- 可以发现,代码中有很多相似和重复的部分,把它抽取出来成为抽象基类
public abstract class Goods {
// 算法骨架
public final void main() {
step1();
step2();
step3();
step4();
}
public abstract void step1();
public abstract void step2();
public void step3() {
System.out.println("调用入库接口");
}
public void step4() {
System.out.println("记录日志");
}
}
// 电器
public class Electric extends Goods {
@Override
public void step1() {
System.out.println("接收电器信息");
}
@Override
public void step2() {
System.out.println("拼接电器请求报文");
}
}
// 衣服
public class Clothes extends Goods {
@Override
public void step1() {
System.out.println("接收衣服信息");
}
@Override
public void step2() {
System.out.println("拼接衣服请求报文");
}
}
// 家具
public class Furniture extends Goods {
@Override
public void step1() {
System.out.println("接收家具信息");
}
@Override
public void step2() {
System.out.println("拼接家具请求报文");
}
}
- 基类中,main方法加了final,这是因为我们要统一控制步骤,不能让子类重写
- 这就是模板方法模式了,下面是一些扩展
- 如果货物入库后,希望针对指定货物对客户进行短信提醒功能
public abstract class Goods {
// 算法骨架
public final void main() {
step1();
step2();
step3();
step4();
if (remind()) {
step5();
}
}
public abstract void step1();
public abstract void step2();
public void step3() {
System.out.println("调用入库接口");
}
public void step4() {
System.out.println("记录日志");
}
public void step5() {
System.out.println("提醒客户");
}
// 默认为false,即不提醒客户,子类根据需要重写
public boolean remind() {
return false;
}
}
// 家具
public class Furniture extends Goods {
@Override
public void step1() {
System.out.println("接收家具信息");
}
@Override
public void step2() {
System.out.println("拼接家具请求报文");
}
// 需要提醒客户
@Override
public boolean remind() {
return true;
}
}
- 在基类中,加入一个判断条件,默认false,再加上一个提醒客户的方法
- 子类根据自己的需要,需要提醒客户的,则重写判断条件方法即可
与策略模式的区别
- 关注点不一样:模板方法关注的是算法大纲,而策略模式关注的是算法族
- 实现原理不一样:模板方法通过继承实现算法的微调,而策略模式通过组合实现不同算法的切换
- 算法控制权不一样:模板方法通过final禁止了子类修改算法大纲,而策略模式不同算法的实现没有要求
- 依赖程度不一样:模板方法使用的继承,策略模式使用组合,所以模板方法的的依赖程度更高,也就是不够解耦
优点
- 代码复用
- 控制大纲(final)
缺点
- 依赖程度较高,继承
- 类个数增多,一个不同的实现需要多一个类
应用
Arrays.sort()
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// getter setter and toString
}
public class Client {
public static void main(String[] args) {
Student[] arr = new Student[]{
new Student("Tom", 18),
new Student("Jerry", 17),
new Student("Amy", 19)
};
Arrays.sort(arr);
for (Student s : arr) {
System.out.println(s);
}
}
}
运行报错
Exception in thread "main" java.lang.ClassCastException: com.balsam.pattern.templateMethod.Student cannot be cast to java.lang.Comparable
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:316)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:184)
at java.util.Arrays.sort(Arrays.java:1246)
at com.balsam.pattern.templateMethod.Client.main(Client.java:17)
得实现Comparable接口,重写compareTo()方法,也就是你得提供比较的依据
public class Student implements Comparable {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o) {
Student s = (Student) o;
if (this.getAge() > s.getAge()) {
return 1;
} else if (this.getAge() < s.getAge()) {
return -1;
}
return 0;
}
}
客户端代码不变,运行结果
Student{name='Jerry', age=17}
Student{name='Tom', age=18}
Student{name='Amy', age=19}
看上去,这并不是一个合格的模板方法模式的应用,模板方法是通过继承去实现某些步骤的具体化,而这里显然不像,但是,它把排序的算法都实现好了,你只需要根据自己的需要提供比较的依据,就可以实现排序的功能,这正是模板方法模式的精神所在。
学习设计模式,不应该完全生搬硬套,得灵活运用