1. 适用场景
1)一次性实现一个算法的不变的部分,并将可变行为留给子类来实现。
2) 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复
模板模式可以帮助我们规范执行流程和执行逻辑,下面演示一个小demo
课程模板类:NetworkSource
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("发布预习资料");}}
课程模板类是一个抽象类,为什么要用抽象类,主要是为了定义一些固定的流程,还有一些方法写成抽象方法可以作为个性化需求的使用,可以润色,可以自由编写,但是核心的模块是不能动的,这也是为什么不使用接口的原因!可以看到这里面流程化的步骤一共分为六大步,前五步都是固定的final不可变,只有第六步布置作业可以叫它钩子函数,可以选择!
java课程:JavaCourse
public class JavaCourse extends NetworkCourse{@Overridevoid checkHomework() {System.out.println("检查java的架构课件");}}
java课程类这里写的是没有布置作业,默认返回的needHomework为false
大数据课程类:BigDataSource
public class BigDataSource extends NetworkCourse{private boolean needHomeworkFlag = false;public BigDataSource(boolean needHomeworkFlag) {this.needHomeworkFlag = needHomeworkFlag;}@Overridevoid checkHomework() {System.out.println("检查大数据的课后作业!");}@Overrideprotected boolean needHomework() {return this.needHomeworkFlag;}}
大数据课程类做了一些更改,布置了作业需要检查,所以这里重写了needHomework方法,我们后面可以传入true
NetworkSourceTest课程验证
public class NetworkCourseTest {public static void main(String[] args) {System.out.println("----java架构师课程----");NetworkCourse javaCourse = new JavaCourse();javaCourse.createCourse();System.out.println("----大数据架构师课程----");NetworkCourse bigDataSource = new BigDataSource(true);bigDataSource.createCourse();}}
可以看到在大数据传值的时候,传入了是否需要布置作业为true!
运行结果:
----java架构师课程----发布预习资料创建备课PPT在线授课!提交课件和笔记提交源代码!----大数据架构师课程----发布预习资料创建备课PPT在线授课!提交课件和笔记提交源代码!检查大数据的课后作业!
这样流程化的东西并没有被改变,针对于不同的课程做一些微调,实现了模板的效果,想想如果换成接口的话,所有的流程全部重写,那么也就不是模板了!另外提醒一下钩子方法:提供对流程的微调
2. JDBC模板
jdbc我们已经很熟悉了,其实底层使用了模板模式进行sql语句的增删改查,只不过对部分入参进行了过滤!
下面我们尝试着编写模拟的jdbc模板,从数据库连接到存入数据库,它主要分为几个步骤:
- 获取数据库连接
- 编写sql
- 执行sql语句
- 处理结果集
- 关闭结果集
- 关闭语句集
- 关闭连接
JdbcTemplate模板
public abstract class JdbcTemplate {private DataSource dataSource;public JdbcTemplate(DataSource dataSource){this.dataSource = dataSource;}public List<?> execteQuery(String sql,RowMapper<?> rowMapper,Object[] values){try {//1.获取链接Connection conn = this.getConnection();//2.创建语句集PreparedStatement pm = this.createPrepareStatement(conn,sql);//3.执行语句ResultSet rs = this.execteQuery(pm,values);//4.处理结果集List<?> result = this.pareseResultSet(rs,rowMapper);//5.关闭结果集this.closeResultSet(rs);//6.关闭语句集this.closeStatement(pm);//7.关闭链接this.closeConnection(conn);return result;} catch (Exception e) {e.printStackTrace();}return null;}protected void closeConnection(Connection conn) throws SQLException {//数据库连接池 不关闭conn.close();};protected void closeStatement(PreparedStatement pm) throws SQLException {pm.close();;};protected void closeResultSet(ResultSet rs) throws SQLException {rs.close();};protected List<?> pareseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {//迭代List<Object> result = new ArrayList<>();int rowNum = 1;while (rs.next()){result.add(rowMapper.mapRow(rs,rowNum++));}return result;};private ResultSet execteQuery(PreparedStatement pm, Object[] values) throws SQLException {for (int i=0;i< values.length;i++){pm.setObject(i,values);}return pm.executeQuery();}protected PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {return conn.prepareStatement(sql);}public Connection getConnection() throws SQLException {return this.dataSource.getConnection();};}
jdbc模板中核心方法为execteQuery,里面包含了几个核心步骤!几个参数需要注意一下
sql:核心执行sql
rowMapper:这个用于对象封装传入的参数,这个对象会从rs中取出,完成rowMapper封装
values:默认为null,可传入参数
RowMapper<T> _orm映射定制化的接口_
public interface RowMapper<T> {T mapRow(ResultSet rs,int rowNum) throws Exception;}
定制化映射的接口,很明显是对结果集进行一对一的映射,方便后续的对象一对一复制转化!
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;}@Overridepublic String toString() {return "Member{" +"username='" + username + '\'' +", password='" + password + '\'' +", nickName='" + nickName + '\'' +", age=" + age +", addr='" + addr + '\'' +'}';}}
Member不用多说,测试用例的属性类
dao层的MemberDao
public class MemberDao extends JdbcTemplate {public MemberDao(DataSource dataSource) {super(dataSource);}public List<?> selectAll(){String sql = "select* from t_member";return super.execteQuery(sql, new RowMapper<Member>() {@Overridepublic 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"));member.setNickName(rs.getString("nickName"));return member;}},null);}}
这个核心dao就是我们平常使用的调用数据库层的接口,其实这里也就是对数据包括参数和对象的封装,然后传给jdbc模板进行数据库查询,返回结果集!这样一个jdbc模板我们就差不多写完了,可能我们平常写模板类的场景确实很少,大多数我们所看到的都是业务之间的问题,只有一些底层实现会注重模板的编写,了解并看清本质。
3. 模板模式的缺点
- 类数目的增加
- 间接地增加了系统实现的复杂度
- 继承关系自身缺点,如果父类关系添加新的抽象方法,所有子类都要改一遍
