一.模板模式概念
在Java中实现某类事情的步骤有些是固定的,有些是会发生变化的,因此出现了模板方法这一概念。模板模式在Java中一般才用abstract实现,我们将固定的步骤在抽象类中实现,将根据需要变化的部分设置为抽象方法,让其实现类来根据自己的需要实现(必须实现),在标准的模板方法模式实现中,主要是使用继承的方式,来让父类在运行期间可以调用到子类的方法。
二.模板模式实现步骤:
1.写先出解决该类事情的一个具体例子的解决方案(也就是将这个问题特殊化,提出一种解决方案,并写出相应的代码);
2.分析代码,把会发生变化的代码抽取出来独立成一个方法,把该方法描述成一个抽象的方法;
3.使用final修饰模板方法,防止别人重写模板方法。
我们用一个很简单的代码来表述一下:
public abstract class Template {
//模板方法
public final void templateMethod(){
method1();
method2();//勾子方法
method3();//抽象方法
}
private void method1(){
System.out.println("父类实现业务逻辑");
}
public void method2(){
System.out.println("父类默认实现,子类可覆盖");
}
protected abstract void method3();//子类负责实现业务逻辑
}
父类中有三个方法,分别是method1(),method2()和method3()。
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。
method2()是所谓的勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。
method3()是子类必须实现的方法,即制定的步骤。
由此可看出,算法的流程执行顺序是由父类掌控的,子类是根据自己的需要完成自己的代码部分来配合总体流程实现。
接下来用一个子类来继承模板类。
public class TemplateImpl extends Template {
@Override
protected void method3() {
System.out.println("method3()在子类TemplateImpl中实现了!!");
}
}
在这里子类只覆盖了自己必须覆盖(模板类的abstract方法)的部分。
我们来测试一下:
Template t1 = new TemplateImpl();
t1.templateMethod();
//结果是:
父类实现业务逻辑
父类默认实现,子类可覆盖
method3()在子类TemplateImpl中实现了!!
可以看出这里TemplateImpl根据需要在必须实现的method3()中添加了自己的功能模块,method2()方法父类已经提供了默认实现,如果子类觉得有必要更改的实现,也可以根据自己的需要来实现相应的功能模块。
Spring--模板模式
在之前接触spring的时候使用了JdbcTemplate方法,当时就觉得spring中的设计模式真的是太流弊了。模板方法(template method)在spring中也是被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包围的包装等都无疑使用了模板模式,但spring并不是单纯使用了模板方法,而是在此基础上做了创新,配合callback(回调)一起使用,用得极其灵活。 回调模式这里不进行讲解,下一节再进行讲解。
spring通过封装JDBC API,对外提供jdbcTemplate实现了代码的灵活复用,大大提高了程序员的开发效率。现在假设一下spring没有采用模板模式会怎么样呢?如下是一段曾经堪称经典的JDBC API代码:
public List<User> query() {
List<User> userList = new ArrayList<User>();
String sql = "select * from User";
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = HsqldbUtil.getConnection();
pst = con.prepareStatement(sql);
rs = pst.executeQuery();
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return userList;
}
可以看出在这段简单的User表查询代码中,我们依次做了以下工作:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....
这里只是查询了一个User表所需的代码,如果需要查询其他表呢?当然是也要不断地做重复的工作......可想而知需要多少冗余的代码来完成相同的工作。
此时模板模式就出现了:
通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是需要根据灵活调整的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的。接下来让我们用一个抽象的父类将公共部分封装一下:
public abstract class JdbcTemplate {
//template method
public final Object execute(String sql) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Object result = doInStatement(rs);//abstract method
return result;
}
catch (SQLException ex) {
ex.printStackTrace();
throw ex;
}
finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//implements in subclass
protected abstract Object doInStatement(ResultSet rs);
}
在上面的抽象类中,封装了JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,交由子类实现。
接下来我们定义一个子类,并继承上面的父类:
public class JdbcTemplateUserImpl extends JdbcTemplate {
@Override
protected Object doInStatement(ResultSet rs) {
List<User> userList = new ArrayList<User>();
try {
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
我们在子类的doInStatement()方法中遍历并封装了结果集返回。
接下来我们只需要来调用方法即可:
String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql);
至此,模板模式就成功了应用到了JDBC API当中,这里只是一张表的查询,查询过程中代码只需要实现查询结果集的封装即可,不需要在新的查询当中再次编写关于创建连接、捕获异常、释放连接这些公共的代码,通过采用模板模式极大地提高了代码复用性。
这里我们通过代码讲解spring中的JdbcTemplate。根据前边模板模式的使用步骤:先将固定化的流程:数据库连接的获取、连接的关闭等功能模块实现。然后将变化的部分(结果集的封装)通过抽象方法交给子类或者回调函数来实现。
模板模式学习过程中参照了一些前人的文章:
https://blog.csdn.net/zhang23242/article/details/9305925
https://blog.csdn.net/jpzhu16/article/details/50888435