模板模式
首先来看一下模板模式的简介:模板模式(Template Pattern),一是指定义一个算法的骨架,并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为性设计模式。模板方法适用于以下应用场景:
1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
生活中的很多小事都是模板模式的体现,比如我们去医院看病的流程:1.挂号 2.看病
3.做检查 4.拿药,下面我们通过代码来体会一下。
首先写一个看病的抽象类SeeDoctor,特别提一下,在这个类中有一个钩子方法needChecked,关于钩子方法的定义:通过一个方法来干涉另一个方法的行为,就是一个方法的返回结果或修改的变量是另一个方法执行时的if判断条件或for/while循环调用条件的。在模板模式中,钩子方法和模板模式没有必然的关系,也就是说模板模式中可以有钩子方法也可以没有钩子方法,具体是否使用钩子方法根据业务判断。
/**
* @author: Winston
* @createTime: 2021/6/28
* <p>
* 模板会有一个或者多个未实现的方法
* 而且这些未实现的方法有固定的执行顺序
* 看病流程
*/
public abstract class SeeDoctor {
/**
* 去医院看病的流程
*/
protected void goToHospital() {
// 1.挂号
this.register();
// 2.找医生看病
this.lookingForDoctor();
// 3.做检查,有些病人是需要做检查的,而有些病人是不需要做检查的
// 这里的needChecked()方法就是钩子方法
if (needChecked()) {
// 检查方法,病人做检查的项目不一样
this.checked();
}
// 4.拿药
this.takeMedicine();
}
public final void takeMedicine() {
System.out.println("根据药方拿药");
}
protected abstract void checked();
// 钩子方法,钩子方法的定义:通过一个方法来干涉另一个方法的行为,就是一个方法的返回结果或修改的变量是另一个方法执行时的if判断条件或for/while循环调用条件的
protected boolean needChecked(){
return false;
}
public final void lookingForDoctor() {
System.out.println("找医生诊断");
}
public final void register() {
System.out.println("挂号");
}
}
编写张三看病类ZhangSanSeeDoctor
public class ZhangSanSeeDoctor extends SeeDoctor {
@Override
protected void checked() {
System.out.println("张三做的检查项目:");
}
}
编写李四看病类LiSiSeeDoctor
public class LiSiSeeDoctor extends SeeDoctor {
/**
* 是否需要检查
*/
private boolean needChecked = false;
/**
* 通过构造方法将needChecked的值改变
* @param needChecked
*/
public LiSiSeeDoctor(boolean needChecked) {
this.needChecked = needChecked;
}
@Override
protected boolean needChecked() {
return this.needChecked;
}
@Override
protected void checked() {
System.out.println("李四做的检查项目:......");
}
}
测试类
public class SeeDoctorTest {
public static void main(String[] args) {
System.out.println("================张三去医院看病========================");
SeeDoctor patient1 = new ZhangSanSeeDoctor();
patient1.goToHospital();
System.out.println("================李四去医院看病========================");
SeeDoctor patient2 = new LiSiSeeDoctor(true);
patient2.goToHospital();
}
}
运行结果:
================张三去医院看病========================
挂号
找医生诊断
根据药方拿药
================李四去医院看病========================
挂号
找医生诊断
李四做的检查项目:......
根据药方拿药
利用模板模式重构JDBC操作业务场景
创建一个末班类JdbcTemplate,封装所有的JDBC操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。我们针对不同的数据,都要封装成不同的实体对象。而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程是不变的,因此,我们可以使用模板方法模式来设计这样的业务场景。
先创建约束ORM逻辑的接口RowMapper
/**
* @author: Winston
* @createTime: 2021/6/28
*
* Spring中RowMapper接口的作用,用来把数据库中的列字段和java bean中属性对应上
*/
public interface RowMapper<T> {
public T mapRow(ResultSet resultSet, int rowNumber);
}
创建封装了所有处理流程的抽象类JdbcTemplate
/**
* @author: Winston
* @createTime: 2021/6/28
*/
public abstract class JdbcTemplate {
/**
* 数据源
*/
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<?> executeQuery(String sql, RowMapper rowMapper, Object... args) throws Exception {
// 1.获取连接
Connection connection = this.getConnection();
// 2.创建语句集
PreparedStatement preparedStatement = this.createPrepareStatement(connection, sql);
// 3.执行语句集
ResultSet resultSet = this.executeQuery(preparedStatement, args);
// 4.处理结果集
List<?> result = this.parseResultSet(resultSet, rowMapper);
// 5.关闭结果集
this.closeResult(resultSet);
// 6.关闭语句集
this.closePreparedStatement(preparedStatement);
// 7.关闭连接
this.closeConnection(connection);
return result;
}
protected void closeConnection(Connection connection) throws SQLException {
connection.close();
}
protected void closePreparedStatement(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.close();
}
protected void closeResult(ResultSet resultSet) throws SQLException {
resultSet.close();
}
protected List<?> parseResultSet(ResultSet resultSet, RowMapper rowMapper) throws SQLException {
List<Object> result = new ArrayList<>();
int rowNum = 1;
while (resultSet.next()) {
result.add(rowMapper.mapRow(resultSet, rowNum++));
}
return result;
}
protected ResultSet executeQuery(PreparedStatement preparedStatement, Object[] args) throws SQLException {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i, args[i]);
}
return preparedStatement.executeQuery();
}
protected PreparedStatement createPrepareStatement(Connection connection, String sql) throws SQLException {
return connection.prepareStatement(sql);
}
protected Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
}
创建实体类User
public class User {
private String username;
private String password;
private String mobile;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
创建数据库操作类UserDao
/**
* @author: Winston
* @createTime: 2021/6/28
*/
public class UserDao extends JdbcTemplate{
public UserDao(DataSource dataSource) {
super(dataSource);
}
public List<?> selectAll() throws Exception {
String sql = "SELECT * FROM t_user";
return super.executeQuery(sql, new RowMapper() {
@Override
public Object mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
// 将实体类和数据库字段对应上
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String mobile = resultSet.getString("mobile");
Integer age = resultSet.getInt("age");
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setMobile(mobile);
user.setAge(age);
return user;
}
}, null);
}
}
测试类,这里由于没有配置Spring环境和数据库环境,仅做模拟。
public class UserTest {
public static void main(String[] args) {
DataSource dataSource = null;
UserDao userDao = new UserDao(dataSource);
try {
List<?> result = userDao.selectAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
模板模式在JDK源码中的体现
先来看看JDK中AbstractList的get()方法,可以看到get()是一个抽象方法,我们经常用的ArrayList就是AbstractList的子类,由于AbstractList不同的子类所实现的get()方法有所不同,所以这也是模板模式的一种体现。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
abstract public E get(int index);
...
}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
...
}
模板模式小结
模板模式就好比是造房子,大致的步骤是规定好的,比如先打地基、然后买水泥黄沙等,至于房子盖的形状可以不同。
模板模式的优缺点
优点:
1、利用模板方法将相同处理逻辑的代码放到抽象类中,可以提高代码的复用性。
2、将不同的代码在不同的子类中实现,通过对子类的扩展增加新的行为,提高代码的扩展性。
3、把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
1、类数目的增加,每一个抽象类都需要至少一个子类来实现,这样导致类的个数增加,从而导致系统复杂度的增加。
2、继承关系自身的缺点,如果父类添加了新的抽象方法,所有子类都要增加该方法。