一.模板模式概念
在Java中實現(xiàn)某類事情的步驟有些是固定的,有些是會發(fā)生變化的,因此出現(xiàn)了模板方法這一概念。模板模式在Java中一般才用abstract實現(xiàn),我們將固定的步驟在抽象類中實現(xiàn),將根據(jù)需要變化的部分設(shè)置為抽象方法,讓其實現(xiàn)類來根據(jù)自己的需要實現(xiàn)(必須實現(xiàn)),在標(biāo)準(zhǔn)的模板方法模式實現(xiàn)中,主要是使用繼承的方式,來讓父類在運行期間可以調(diào)用到子類的方法。
二.模板模式實現(xiàn)步驟:
1.寫先出解決該類事情的一個具體例子的解決方案(也就是將這個問題特殊化,提出一種解決方案,并寫出相應(yīng)的代碼);
2.分析代碼,把會發(fā)生變化的代碼抽取出來獨立成一個方法,把該方法描述成一個抽象的方法;
3.使用final修飾模板方法,防止別人重寫模板方法。
我們用一個很簡單的代碼來表述一下:
public abstract class Template {
//模板方法
public final void templateMethod(){
method1();
method2();//勾子方法
method3();//抽象方法
}
private void method1(){
System.out.println("父類實現(xiàn)業(yè)務(wù)邏輯");
}
public void method2(){
System.out.println("父類默認(rèn)實現(xiàn),子類可覆蓋");
}
protected abstract void method3();//子類負責(zé)實現(xiàn)業(yè)務(wù)邏輯
}
父類中有三個方法,分別是method1(),method2()和method3()。
method1()是私有方法,有且只能由父類實現(xiàn)邏輯,由于方法是private的,所以只能父類調(diào)用。
method2()是所謂的勾子方法。父類提供默認(rèn)實現(xiàn),如果子類覺得有必要定制,則可以覆蓋父類的默認(rèn)實現(xiàn)。
method3()是子類必須實現(xiàn)的方法,即制定的步驟。
由此可看出,算法的流程執(zhí)行順序是由父類掌控的,子類是根據(jù)自己的需要完成自己的代碼部分來配合總體流程實現(xiàn)。
接下來用一個子類來繼承模板類。
public class TemplateImpl extends Template {
@Override
protected void method3() {
System.out.println("method3()在子類TemplateImpl中實現(xiàn)了??!");
}
}
在這里子類只覆蓋了自己必須覆蓋(模板類的abstract方法)的部分。
我們來測試一下:
Template t1 = new TemplateImpl();
t1.templateMethod();
//結(jié)果是:
父類實現(xiàn)業(yè)務(wù)邏輯
父類默認(rèn)實現(xiàn),子類可覆蓋
method3()在子類TemplateImpl中實現(xiàn)了?。?
可以看出這里TemplateImpl根據(jù)需要在必須實現(xiàn)的method3()中添加了自己的功能模塊,method2()方法父類已經(jīng)提供了默認(rèn)實現(xiàn),如果子類覺得有必要更改的實現(xiàn),也可以根據(jù)自己的需要來實現(xiàn)相應(yīng)的功能模塊。
Spring--模板模式
在之前接觸spring的時候使用了JdbcTemplate方法,當(dāng)時就覺得spring中的設(shè)計模式真的是太流弊了。模板方法(template method)在spring中也是被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無疑使用了模板模式,但spring并不是單純使用了模板方法,而是在此基礎(chǔ)上做了創(chuàng)新,配合callback(回調(diào))一起使用,用得極其靈活。 回調(diào)模式這里不進行講解,下一節(jié)再進行講解。
spring通過封裝JDBC API,對外提供jdbcTemplate實現(xiàn)了代碼的靈活復(fù)用,大大提高了程序員的開發(fā)效率?,F(xiàn)在假設(shè)一下spring沒有采用模板模式會怎么樣呢?如下是一段曾經(jīng)堪稱經(jīng)典的JDBC API代碼:
public List<User> query() {
List<User> userList = new ArrayList<User>();
String sql = "select * from User";
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = HsqldbUtil.getConnection();
pst = con.prepareStatement(sql);
rs = pst.executeQuery();
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return userList;
}
可以看出在這段簡單的User表查詢代碼中,我們依次做了以下工作:
1、獲取connection
2、獲取statement
3、獲取resultset
4、遍歷resultset并封裝成集合
5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常
6、.....
這里只是查詢了一個User表所需的代碼,如果需要查詢其他表呢?當(dāng)然是也要不斷地做重復(fù)的工作......可想而知需要多少冗余的代碼來完成相同的工作。
此時模板模式就出現(xiàn)了:
通過觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是需要根據(jù)靈活調(diào)整的,因為每張表都映射不同的java bean。這部分代碼是沒有辦法復(fù)用的。接下來讓我們用一個抽象的父類將公共部分封裝一下:
public abstract class JdbcTemplate {
//template method
public final Object execute(String sql) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
Object result = doInStatement(rs);//abstract method
return result;
}
catch (SQLException ex) {
ex.printStackTrace();
throw ex;
}
finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(!con.isClosed()){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//implements in subclass
protected abstract Object doInStatement(ResultSet rs);
}
在上面的抽象類中,封裝了JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,交由子類實現(xiàn)。
接下來我們定義一個子類,并繼承上面的父類:
public class JdbcTemplateUserImpl extends JdbcTemplate {
@Override
protected Object doInStatement(ResultSet rs) {
List<User> userList = new ArrayList<User>();
try {
User user = null;
while (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setBirth(rs.getDate("birth"));
user.setCreateDate(rs.getDate("create_date"));
userList.add(user);
}
return userList;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
我們在子類的doInStatement()方法中遍歷并封裝了結(jié)果集返回。
接下來我們只需要來調(diào)用方法即可:
String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql);
至此,模板模式就成功了應(yīng)用到了JDBC API當(dāng)中,這里只是一張表的查詢,查詢過程中代碼只需要實現(xiàn)查詢結(jié)果集的封裝即可,不需要在新的查詢當(dāng)中再次編寫關(guān)于創(chuàng)建連接、捕獲異常、釋放連接這些公共的代碼,通過采用模板模式極大地提高了代碼復(fù)用性。
這里我們通過代碼講解spring中的JdbcTemplate。根據(jù)前邊模板模式的使用步驟:先將固定化的流程:數(shù)據(jù)庫連接的獲取、連接的關(guān)閉等功能模塊實現(xiàn)。然后將變化的部分(結(jié)果集的封裝)通過抽象方法交給子類或者回調(diào)函數(shù)來實現(xiàn)。
模板模式學(xué)習(xí)過程中參照了一些前人的文章:
https://blog.csdn.net/zhang23242/article/details/9305925
https://blog.csdn.net/jpzhu16/article/details/50888435