模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
介绍
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
何时使用:有一些通用的方法。
如何解决:将这些通用算法抽象出来。
关键代码:在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
应用场景
我们以课程创建流程为例:发布预习资料-->制作课件PPT-->在线直播-->提交课堂笔记-->提交源码-->布置作业-->检查作业。首先我们来创建
NetworkCourse抽象类:
public abstract class NetworkCourse {
protected final void createCourse(){
//1、发布预习资料
this.postPreResource();
//2、制作PPT课件
this.createPPT();
//3、在线直播
this.liveVideo();
//4、提交课件、课堂笔记
this.postNote();
//5、提交源码
this.postSource();
//6、布置作业,有些课是没有作业,有些课是有作业的
//如果有作业的话,检查作业,如果没作业,完成了
if(needHomework()){
checkHomework();
}
}
abstract void checkHomework();
//钩子方法:实现流程的微调
protected boolean needHomework(){return false;}
final void postSource(){
System.out.println("提交源代码");
}
final void postNote(){
System.out.println("提交课件和笔记");
}
final void liveVideo(){
System.out.println("直播授课");
}
final void createPPT(){
System.out.println("创建备课PPT");
}
final void postPreResource(){
System.out.println("分发预习资料");
}
}
上面的代码中有个钩子方法可能有些小伙伴还不是太理解,在此我稍作解释。设计钩子方法的主要目的是用来干预执行流程,使得我们控制行为流程更加灵活,更符合实际业务的需求。钩子方法的返回值一般为适合条件分支语句的返回值(如boolean、int等)。小伙伴们可以根据自己的业务场景来决定是否需要使用钩子方法。接下来创建JavaCourse类:
public class JavaCourse extends NetworkCourse {
void checkHomework() {
System.out.println("检查Java的架构课件");
}
}
创建BigDataCourse类:
public class BigDataCourse extends NetworkCourse {
private boolean needHomeworkFlag = false;
public BigDataCourse(boolean needHomeworkFlag) {
this.needHomeworkFlag = needHomeworkFlag;
}
void checkHomework() {
System.out.println("检查大数据的课后作业");
}
@Override
protected boolean needHomework() {
return this.needHomeworkFlag;
}
}
客户端测试代码:
public class NetworkCourseTest {
public static void main(String[] args) {
System.out.println("---Java架构师课程---");
NetworkCourse javaCourse = new JavaCourse();
javaCourse.createCourse();
System.out.println("---大数据课程---");
NetworkCourse bigDataCourse = new BigDataCourse(true);
bigDataCourse.createCourse();
}
}
通过这样一个案例,相信下伙伴们对模板模式有了一个基本的印象。为了加深理解,下面我们来结合一个常见的业务场景。
利用模板模式重构JDBC操作业务场景
创建一个模板类JdbcTemplate,封装所有的JDBC操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。我们针对不同的数据,都要封装成不同的实体对象。而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程是不变的,因此,我们可以使用模板方法模式来设计这样的业务场景。先创建约束 ORM 逻辑的接口RowMapper:
public interface RowMapper<T> {
T mapRow(ResultSet rs,int rowNum) throws Exception;
}
在创建封装了所有处理流程的抽象类JdbcTemplate:
public abstract class JdbcTemplate {
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<?> executeQuery(String sql, RowMapper<?> rowMapper, Object[] values){
try {
//1、获取连接
Connection conn = this.getConnection();
//2、创建语句集
PreparedStatement pstm = this.createPrepareStatement(conn,sql);
//3、执行语句集
ResultSet rs = this.executeQuery(pstm,values);
//4、处理结果集
List<?> result = this.paresResultSet(rs,rowMapper);
//5、关闭结果集
this.closeResultSet(rs);
//6、关闭语句集
this.closeStatement(pstm);
//7、关闭连接
this.closeConnection(conn);
return result;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
protected void closeConnection(Connection conn) throws Exception {
//数据库连接池,我们不是关闭
conn.close();
}
protected void closeStatement(PreparedStatement pstm) throws Exception {
pstm.close();
}
protected void closeResultSet(ResultSet rs) throws Exception {
rs.close();
}
protected List<?> paresResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
List<Object> result = new ArrayList<Object>();
int rowNum = 1;
while (rs.next()){
result.add(rowMapper.mapRow(rs,rowNum ++));
}
return result;
}
protected ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws Exception {
for (int i = 0; i < values.length; i++) {
pstm.setObject(i,values[i]);
}
return pstm.executeQuery();
}
protected PreparedStatement createPrepareStatement(Connection conn, String sql) throws Exception {
return conn.prepareStatement(sql);
}
public Connection getConnection() throws Exception {
return this.dataSource.getConnection();
}
}
创建实体对象Member类:
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;
}
}
创建数据库操作类MemberDao:
public class MemberDao extends JdbcTemplate {
public MemberDao(DataSource dataSource) {
super(dataSource);
}
public List<?> selectAll(){
String sql = "select * from t_member";
return super.executeQuery(sql, new RowMapper<Member>() {
public Member mapRow(ResultSet rs, int rowNum) throws Exception {
Member member = new Member();
//字段过多,原型模式
member.setUsername(rs.getString("username"));
member.setPassword(rs.getString("password"));
member.setAge(rs.getInt("age"));
member.setAddr(rs.getString("addr"));
return member;
}
},null);
}
}
客户端测试代码:
public class MemberDaoTest {
public static void main(String[] args) {
MemberDao memberDao = new MemberDao(null);
List<?> result = memberDao.selectAll();
System.out.println(result);
}
}
模板模式在源码中的体现
先来看JDK中的AbstractList,来看代码:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
abstract public E get(int index);
...
}
我们看到get()是一个抽象方法,那么它的逻辑就是交给子类来实现,我们大家所熟知的ArrayList 就是 AbstractList 的子类。同理,有 AbstractList 就有 AbstractSet 和AbstractMap,有兴趣的小伙伴可以去看看这些的源码实现。还有一个每天都在用的HttpServlet,有三个方法service()和doGet()、doPost()方法,都是模板方法的抽象实现。在 MyBatis框架也有一些经典的应用,我们来一下 BaseExecutor 类,它是一个基础的SQL执行类,实现了大部分的SQL执行逻辑,然后把几个方法交给子类定制化完成,源码如下:
public abstract class BaseExecutor implements Executor {
...
protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean var1) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4) throws SQLException;
...
}
图片.png如 doUpdate、doFlushStatements、doQuery、doQueryCursor 这几个方法就是交由子类来实现,那么BaseExecutor有哪些子类呢?我们来看一下它的类图:
我们一起来看一下SimpleExecutor的doUpdate实现:
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
再来对比一下BatchExecutor的doUpate实现:
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);//fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// handler.parameterize(stmt);
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
细心的小伙伴一定看出来了差异。当然,我们在这里就暂时不对MyBatis源码进行深入分析。
一些信息
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
邮箱:xihuady@126.com
网友评论