概念
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
当项目中出现流程长,但由于在不同场景下具体的实现不同,那么就可以定义算法骨架,即不同场景下共有的逻辑,在需要不同实现的位置抽象为方法,由子类去实现。
模板设计模式解决问题的是,复用 与 扩展。
好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。
使用方式
关键点:
- 定义算法骨架,抽象为父类;
- 子类继承父类,由子类实现不同场景下的逻辑;
举个例子
以考试卷子为例,一份试卷需要复印多份给学生去考试,卷子的试题都是一样的,而每一份试卷的简答和名字都不相同。那么就可以抽象出这样的父类。
abstract class Paper {
abstract String getName(); //获取学生姓名
abstract String getResult(); //获取学生的答案
public void getPaperContent() { //获取试卷的试题内容
System.out.println("我是"+ getName() + ",我的答案是"+ getResult());
//....
}
}
那么学生的试卷就可以继承这个父类,具体实现就交给子类去是实现了。
class StudentPaPer1 extends Paper {
@Override
public String getName() {
return "张三";
}
@Override
public String getResult(){
return "a";
}
}
class StudentPaPer2 extends Paper {
@Override
public String getName() {
return "李四";
}
@Override
public String getResult(){
return "b";
}
}
那么调用方式就变为
public static void main(String[] args) {
Paper student1 = new StudentPaPer1();
Paper student2 = new StudentPaPer2();
student1.getPaperContent();
student2.getPaperContent();
//...
}
PS:其实就是利用继承的特性将共有逻辑封装起来;
扩展
使用模板设计模式可以减少重复代码,通过定义抽象方法,由子类实现。使用**回调(Callback)**同样可以起到复用与扩展的作用。
回调的概念
相对于普通的函数调用来说,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是“回调函数”。A 调用 B,B 反过来又调用 A,这种调用机制就叫作“回调”。
A类如何将回调函数注册给B类呢?在Java中可以将A类的回调对象传入到B类中。
public interface ICallback {
void methodToCallback();
}
/**
* 封装公有逻辑
**/
public class B {
public void process(ICallback callback) {
//...
callback.methodToCallback();
//...
}
}
// 回调实现类
public class A implements ICallback {
@Override
public void methodToCallback() {
System.out.println("call back invoke...");
}
}
// 客户端调用
public class Demo {
public static void main(String[] args) {
B b = new B();
// 使用匿名创建对象
b.prpcess(new ICallback(){
@Override
public void methodToCallback() {
System.out.println("call back invoke...");
}
});
// 也可以直接创建A对象实例
A a = new A();
b.process(a);
}
}
在Demo客户端中,创建了B类对象,在调用B#process方法时,创建匿名对象当作是回调对象传给process方法,这样B在执行process中可执行特定的场景方法;
后续有新增的场景,那么只需要创建实现 ICallback
回调类的对象即能实现代码复用及扩展。
场景一:JdbcTemplate
public class JdbcTemplateDemo {
private JdbcTemplate jdbcTemplate;
public User queryUser(long id) {
String sql = "select * from user where id="+id;
return jdbcTemplate.query(sql, new UserRowMapper()).get(0);
}
class UserRowMapper implements RowMapper<User> {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setTelephone(rs.getString("telephone"));
return user;
}
}
}
JdbcTemplate 里面复用了加载驱动、创建数据库连接、创建 statement、关闭连接、关闭 statement、处理异常等繁琐的流程步骤;针对不同的SQL,其流程代码都是可以复用的;在使用的时候只需要创建RowMapper的实现,将对象当作是回调对象传给方法,从而实现代码复用与扩展。
场景二:JVM的钩子方法
JVM 提供了 Runtime.addShutdownHook(Thread hook) 方法,可以注册一个 JVM 关闭的 Hook。Hook即是钩子,在应用程序关闭的时候,JVM会自动调用Hook代码。
public class ShutdownHookDemo {
private static class ShutdownHook extends Thread {
public void run() {
System.out.println("I am called during shutting down.");
}
}
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
}
}
有关 Hook 的逻辑都被封装到 ApplicationShutdownHooks 类中了。当应用程序关闭的时候,JVM 会调用这个类的 runHooks() 方法,创建多个线程,并发地执行多个 Hook。我们在注册完 Hook 之后,并不需要等待 Hook 执行完成,所以,这也算是一种异步回调。
模板设计与回调方法的区别与联系
从代码实现上,回调利用组合关系实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板利用继承关系实现,子类重写父类的抽象方法,是一种类之间的关系;
可以看出,回调相对于模板模式会更加灵活,主要体现在以下几点:
- Java是单继承的,基于模板模式编写的子类,不再具有继承的特点;
- 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类;
- 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。