模板方法模式
定义
是指定义一个操作中的算法框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,属于行为型模式。
模板方法模式实际上是封装了一个固定流程,该流程由几个步骤组成,具体步骤可以由子类进行不同实现,从而让固定的流程产生不停的结构。它非常简单,其实就是类的继承机制,但它缺是一个应用非常广泛的模式。模板方法模式的本质就是抽象封装流程,具体进行实现。
适用情景
当完成一个操作具有固定的流程,由抽象流程步骤,具体步骤交给子类进行具体实现(固定的流程,不同的实现)
- 一次性实现一个算法的不同部分,并将可变的行为留给子类实现。
- 各个类中公共行为被提取出来并集中到一个公共父类中,从而避免代码的重复。
角色
- 抽象模板(AbstractClass):抽象模板定义了一套算法框架/流程。
- 具体实现(ConcreteClass):对算法框架/流程的某些步骤进行了实现。
代码演示
public abstract class AbstractCourse {
public final void createCourse(){
postPreResource();
createPPT();
liveVideo();
postResource();
postHomework();
if (needCheckHomework()){
checkHomework();
}
}
protected boolean needCheckHomework(){
return false;
}
protected void postPreResource(){
System.out.println("发布预习资料");
}
protected void createPPT(){
System.out.println("制作ppt");
}
protected void liveVideo(){
System.out.println("直播授课");
}
protected void postResource(){
System.out.println("上传课后资料");
}
protected void postHomework(){
System.out.println("布置作业");
}
protected void checkHomework(){
System.out.println("检查作业");
}
}
public class JavaCourse extends AbstractCourse{
private boolean needCheckHomework = false;
public void setNeedCheckHomework(boolean needCheckHomework) {
this.needCheckHomework = needCheckHomework;
}
@Override
protected boolean needCheckHomework() {
return this.needCheckHomework;
}
protected void checkHomework(){
System.out.println("检查java作业");
}
}
public class test {
public static void main(String[] args) {
JavaCourse course = new JavaCourse();
course.setNeedCheckHomework(true);
course.createCourse();
}
}
关于钩子方法needcheckHomework()
设计钩子方法的主要目的是用来干预执行流程,使我们控制流程更加灵活,更符合实际业务需求。钩子的返回值一般为审核条件分支语句的返回值(如boolean,int等)
利用模板方法模式重构JDBC操作业务场景
创建一个模板类JDBCTemplate,封装所有的JDBC操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。针对不同的数据,都要封装成不同的额实体对象。而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程不变,因此,使用模板方法模式来设计。
public interface RowMapper<T> {
T mapRow(ResultSet re, int rowNum);
}
public abstract class JdbcTemplate {
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
public final List<?> executeQuery(String sql, RowMapper<?> rowMapper, Object[] values) {
try {
Connection conn = this.getConnection();
PreparedStatement pstm = this.createPreparedStatement(conn, sql);
ResultSet rs = this.executeQuery(pstm, values);
List<?> result = this.parseResultSet(rs, rowMapper);
rs.close();
pstm.close();
conn.close();
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
private PreparedStatement createPreparedStatement(Connection conn, String sql) throws SQLException {
return conn.prepareStatement(sql);
}
private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
for (int i = 0; i < values.length; i++) {
pstm.setObject(i, values[i]);
}
return pstm.executeQuery();
}
private List<?> parseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws SQLException {
List<Object> result = new ArrayList<Object>();
int rowNum = 0;
while (rs.next()) {
result.add(rowMapper.mapRow(rs, rowNum++));
}
return result;
}
}
public class Member {
private String username;
private String password;
private String nickname;
private int age;
private String addr;
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 getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
public class JdbcDao extends JdbcTemplate{
public JdbcDao(DataSource dataSource) {
super(dataSource);
}
public List<?> selectAll(){
String sql = "select * from t_merber";
return super.executeQuery(sql, new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet re, int rowNum) {
Member member = new Member();
member.setUsername("");
member.setAge(1);
return member;
}
}, null);
}
}
public class Test {
public static void main(String[] args) {
JdbcDao jdbcDao = new JdbcDao(null);
List<?> result = jdbcDao.selectAll();
}
}
模板方法模式在源码中的使用
JDK中的AbstractList, AbstractSet, AbstractMap
HttpServlet
Mybatis 的BeanExecutor
优点
- 利用模板方法将相同的处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
- 将不同的代码放在不同的子类中,通过对子类的扩展添加新的行为,提供代码的扩展性。
- 把不变的行为写在父类上,去除子类的重复代码,提供一个很好的代码复用平台,符合开闭原则。
缺点
- 类数目增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
- 类数目的增加,间接增加了系统实现的复杂度。
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍