一.模板模式概念
在Java中實(shí)現(xiàn)某類事情的步驟有些是固定的卜范,有些是會發(fā)生變化的弟晚,因此出現(xiàn)了模板方法這一概念。模板模式在Java中一般才用abstract實(shí)現(xiàn),我們將固定的步驟在抽象類中實(shí)現(xiàn)调榄,將根據(jù)需要變化的部分設(shè)置為抽象方法,讓其實(shí)現(xiàn)類來根據(jù)自己的需要實(shí)現(xiàn)(必須實(shí)現(xiàn))呵扛,在標(biāo)準(zhǔn)的模板方法模式實(shí)現(xiàn)中每庆,主要是使用繼承的方式,來讓父類在運(yùn)行期間可以調(diào)用到子類的方法今穿。
二.模板模式實(shí)現(xiàn)步驟:
1.寫先出解決該類事情的一個(gè)具體例子的解決方案(也就是將這個(gè)問題特殊化缤灵,提出一種解決方案,并寫出相應(yīng)的代碼)蓝晒;
2.分析代碼凤价,把會發(fā)生變化的代碼抽取出來獨(dú)立成一個(gè)方法,把該方法描述成一個(gè)抽象的方法拔创;
3.使用final修飾模板方法利诺,防止別人重寫模板方法。
我們用一個(gè)很簡單的代碼來表述一下:
public abstract class Template {
//模板方法
public final void templateMethod(){
method1();
method2();//勾子方法
method3();//抽象方法
}
private void method1(){
System.out.println("父類實(shí)現(xiàn)業(yè)務(wù)邏輯");
}
public void method2(){
System.out.println("父類默認(rèn)實(shí)現(xiàn)剩燥,子類可覆蓋");
}
protected abstract void method3();//子類負(fù)責(zé)實(shí)現(xiàn)業(yè)務(wù)邏輯
}
父類中有三個(gè)方法慢逾,分別是method1(),method2()和method3()灭红。
method1()是私有方法侣滩,有且只能由父類實(shí)現(xiàn)邏輯,由于方法是private的变擒,所以只能父類調(diào)用君珠。
method2()是所謂的勾子方法。父類提供默認(rèn)實(shí)現(xiàn)娇斑,如果子類覺得有必要定制策添,則可以覆蓋父類的默認(rèn)實(shí)現(xiàn)。
method3()是子類必須實(shí)現(xiàn)的方法毫缆,即制定的步驟唯竹。
由此可看出,算法的流程執(zhí)行順序是由父類掌控的苦丁,子類是根據(jù)自己的需要完成自己的代碼部分來配合總體流程實(shí)現(xiàn)浸颓。
接下來用一個(gè)子類來繼承模板類。
public class TemplateImpl extends Template {
@Override
protected void method3() {
System.out.println("method3()在子類TemplateImpl中實(shí)現(xiàn)了!产上!");
}
}
在這里子類只覆蓋了自己必須覆蓋(模板類的abstract方法)的部分棵磷。
我們來測試一下:
Template t1 = new TemplateImpl();
t1.templateMethod();
//結(jié)果是:
父類實(shí)現(xiàn)業(yè)務(wù)邏輯
父類默認(rèn)實(shí)現(xiàn),子類可覆蓋
method3()在子類TemplateImpl中實(shí)現(xiàn)了=痢仪媒!
可以看出這里TemplateImpl根據(jù)需要在必須實(shí)現(xiàn)的method3()中添加了自己的功能模塊,method2()方法父類已經(jīng)提供了默認(rèn)實(shí)現(xiàn)姻僧,如果子類覺得有必要更改的實(shí)現(xiàn)规丽,也可以根據(jù)自己的需要來實(shí)現(xiàn)相應(yīng)的功能模塊。
Spring--模板模式
在之前接觸spring的時(shí)候使用了JdbcTemplate方法撇贺,當(dāng)時(shí)就覺得spring中的設(shè)計(jì)模式真的是太流弊了赌莺。模板方法(template method)在spring中也是被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無疑使用了模板模式松嘶,但spring并不是單純使用了模板方法艘狭,而是在此基礎(chǔ)上做了創(chuàng)新,配合callback(回調(diào))一起使用翠订,用得極其靈活巢音。 回調(diào)模式這里不進(jìn)行講解,下一節(jié)再進(jìn)行講解尽超。
spring通過封裝JDBC API官撼,對外提供jdbcTemplate實(shí)現(xiàn)了代碼的靈活復(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彪杉、.....
這里只是查詢了一個(gè)User表所需的代碼毅往,如果需要查詢其他表呢?當(dāng)然是也要不斷地做重復(fù)的工作......可想而知需要多少冗余的代碼來完成相同的工作在讶。
此時(shí)模板模式就出現(xiàn)了:
通過觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的煞抬,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是需要根據(jù)靈活調(diào)整的构哺,因?yàn)槊繌埍矶加成洳煌膉ava bean。這部分代碼是沒有辦法復(fù)用的。接下來讓我們用一個(gè)抽象的父類將公共部分封裝一下:
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()中,交由子類實(shí)現(xiàn)碟嘴。
接下來我們定義一個(gè)子類溪食,并繼承上面的父類:
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)中错沃,這里只是一張表的查詢,查詢過程中代碼只需要實(shí)現(xiàn)查詢結(jié)果集的封裝即可雀瓢,不需要在新的查詢當(dāng)中再次編寫關(guān)于創(chuàng)建連接枢析、捕獲異常、釋放連接這些公共的代碼刃麸,通過采用模板模式極大地提高了代碼復(fù)用性醒叁。
這里我們通過代碼講解spring中的JdbcTemplate。根據(jù)前邊模板模式的使用步驟:先將固定化的流程:數(shù)據(jù)庫連接的獲取泊业、連接的關(guān)閉等功能模塊實(shí)現(xiàn)把沼。然后將變化的部分(結(jié)果集的封裝)通過抽象方法交給子類或者回調(diào)函數(shù)來實(shí)現(xiàn)。
模板模式學(xué)習(xí)過程中參照了一些前人的文章:
https://blog.csdn.net/zhang23242/article/details/9305925
https://blog.csdn.net/jpzhu16/article/details/50888435