设计模式之 ==> 模板设计模式
一、什么是模板方法模式
定义一个操作中的算法骨架,将一些步骤的具体实现延迟到子类当中去实现。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。
二、模板方法模式的使用场景
- 在多个子类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中。
- 程序主框架相同,细节不同的情况下,也可以使用模板方法。
三、模板方法模式的写法举例
下面我们以一个使用 JDBC 查询 MySQL 数据库的例子来介绍一下模板方法模式,首先我们来看一下最初版的实现:
public class UserQuary { private static final String URL = "jdbc:mysql://xxx.xxx.xxx.xxx:3306/jdbc"; private static final String USER_NAME = "xxxx"; private static final String PASSWORD = "xxxxxx"; public List<User> quary(String sql, List<Object> params) throws Exception { // 1.建立连接 Connection connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD); // 2.建立Statement,并将数据注入预编译sql当中 PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; i < params.size(); i++) { statement.setObject(i + 1, params.get(i)); } // 3.执行sql,获取结果 ResultSet resultSet = statement.executeQuery(); // 4.处理查询结果,将查询结果转换为对应的User对象 List<User> users = new ArrayList<>(); while (resultSet.next()) { User user = User.builder() .id(resultSet.getLong("id")) .name(resultSet.getString("account_id")) .age(resultSet.getInt("account_name")) .build(); users.add(user); } // 5.资源关闭 resultSet.close(); statement.close(); connection.close(); // 返回结果 return users; } }
从以上代码可以看出,这个方法只能查询 user 表,如果我们需要查询其他的表,需要 copy 很多份,在处理查询结果的时候根据对应的表处理成其对应的实体类对象。我们整段代码发现其中总共五个步骤,其中有四个步骤(1,2,3,5)是固定不变的,只有第 4 步对于查询结果的处理,对于不同的表处理的方式不同,对应的实体类模板也不同,那么这就很符合我们上面对模板方法模式的使用场景,我们在父类中实现 1,2,3,5 这四个步骤,第 4 步交给子类去处理。下面我们来进行设计和实现:
首先,定义一个接口 DBHandler,描述查询方法:
public interface DBHandler<T> { /** * 参数说明: * @param sql --> "select * from tableName where column1 = ? and column2 = ?" * @param params --> ["jack", "male"] */ List<T> quary(String sql, List<Object> params); }
由于具体不知道查询什么表,所以返回结果使用一个泛型 T。
然后再来一个抽象类 AbstractDBHandler,把四个固定的步骤(1,2,3,5)在这个父类中实现,而第 4 步使用一个抽象方法交给子类去实现
public abstract class AbstractDBHandler<T> implements DBHandler<T> { protected abstract T getResult(ResultSet resultSet); @Override public List<T> quary(String sql, List<Object> params) { Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; try { // 1.建立连接 connection = getConnection(); // 2.建立statement,并将数据注入预编译sql当中 statement = getStatement(connection, sql, params); // 3.执行查询,获取结果 resultSet = statement.executeQuery(); // 4.处理查询结果,将查询结果转换为对应的实体类对象 List<T> result = new ArrayList<>(); while (resultSet.next()) { T t = getResult(resultSet); result.add(t); } return result; } catch (SQLException e) { throw new IllegalStateException(e); } finally { // 5.关闭资源 release(resultSet, statement, connection); } } private Connection getConnection() { try { return DriverManager.getConnection(ConnectConsts.URL,ConnectConsts.USERNAME,ConnectConsts.PASSWORD); } catch (SQLException e) { throw new IllegalStateException(e); } } private PreparedStatement getStatement(Connection connection, String sql, List<Object> params) throws SQLException { PreparedStatement statement = connection.prepareStatement(sql); for (int i = 0; i < params.size(); i++) { statement.setObject(i + 1, params.get(i)); } return statement; } private void release(ResultSet resultSet, PreparedStatement statement, Connection connection) { try { if (null != resultSet) { resultSet.close(); } if (null != statement) { statement.close(); } if (null != connection) { connection.close(); } } catch (SQLException e) { throw new IllegalStateException(e); } } }
可以看到:第 4 步中只是调用了一下抽线方法 getResult(),具体的实现交给子类。
再来看一下两个子类:AccountDBHandler 和 UserDBHandler
public class AccountDBHandler extends AbstractDBHandler<Account> { @Override protected Account getResult(ResultSet resultSet) { try { return Account.builder() .id(resultSet.getInt("id")) .accountId(resultSet.getString("account_id")) .accountName(resultSet.getString("account_name")) .build(); } catch (SQLException e) { throw new IllegalStateException(e); } } }
public class UserDBHandler extends AbstractDBHandler<User> { @Override protected User getResult(ResultSet resultSet) { try { return User.builder() .id(resultSet.getLong("id")) .name(resultSet.getString("name")) .age(resultSet.getInt("age")) .build(); } catch (SQLException e) { throw new IllegalStateException(e); } } }
我们可以很清楚的看到,两个子类都是只实现了 getResult() 这个方法,不同的表获取不同的列,然后封装成不同的模板对象。
最后来看一下具体的方法调用,分别查询 account 表和 user 表.
public class App { @Test public void test01(){ String sql = "select * from account where id > ?"; ArrayList<Object> params = new ArrayList<>(); params.add(1); DBHandler<Account> accountDbHandler = new AccountDBHandler(); List<Account> accounts = accountDbHandler.quary(sql, params); accounts.forEach(System.out::println); } @Test public void test02(){ String sql = "select * from user where id = ? and name = ?"; ArrayList<Object> params = new ArrayList<>(); params.add(1); params.add("jack"); DBHandler<User> userDbHandler = new UserDBHandler(); List<User> accounts = userDbHandler.quary(sql, params); accounts.forEach(System.out::println); } }
我们只需要构造不同的 sql 和 params 来查询不同的表,使用表对应的实体类模板来封装查询结果。