回調(diào)模式概念
上一節(jié)我們講了一下模板模式的作用,模板模式可以將實(shí)現(xiàn)步驟延遲到子類中進(jìn)行,其實(shí)在Java開發(fā)中,還有另外一個(gè)方法可以實(shí)現(xiàn)同樣的功能,那就是Java回調(diào)技術(shù),通過回調(diào)在接口中定義的方法,調(diào)用到具體的實(shí)現(xiàn)類中的 方法,其本質(zhì)是利用Java的動態(tài)綁定技術(shù),在這種實(shí)現(xiàn)中,可以不把實(shí)現(xiàn)類寫成單獨(dú)的類,而使用內(nèi)部類或匿名內(nèi)部類來實(shí)現(xiàn)回調(diào)方法。
還是拿上一節(jié)的模板模式代碼舉例,如果我每次需要使用jdbcTemplate時(shí),都要繼承一下父類,是不是有些不方便呢? 畢竟一個(gè)類只能繼承一個(gè)父類,但是接口就不一樣了,我們可以實(shí)現(xiàn)很多接口。
那就讓我們甩掉abstract這頂帽子吧,這時(shí),就該callback(回調(diào))上場了
所謂回調(diào),就是在方法參數(shù)中傳遞一個(gè)接口,在調(diào)用此方法時(shí),必須調(diào)用方法中傳遞的接口的實(shí)現(xiàn)類。
回調(diào)模式一般滿足如下條件:
1.類A持有類B的一個(gè)引用,并且類A實(shí)現(xiàn)了一個(gè)接口CallBack;
2.類B有一個(gè)方法method(CallBack callBack),接收一個(gè)參數(shù)callBack,參數(shù)類型為CallBack,在方法method中()調(diào)用了callBack接口的實(shí)現(xiàn)類的方法。
一個(gè)示例代碼如下所示:
interface CallBack {
public void doSomething();
}
class A implements CallBack {
private B b;
A(B b) {
this.b = b;
}
public void test() {
b.testB(this);
}
public void doSomething() {
System.out.println("do something...");
}
}
class B {
public void testB(CallBack callBack) {
System.out.println("========================");
callBack.doSomething();
}
}
public class CallBackDemo {
public static void main(String[] args) {
B b = new B();
A a = new A(b);
a.test();
}
}
上述代碼分析:
這是一個(gè)最簡單的回調(diào)模式的實(shí)現(xiàn)代碼。A實(shí)現(xiàn)餓了CallBack接口,并且在A中有一個(gè)B的引用,B的testB(CallBack callBack)方法接收一個(gè)CallBack的實(shí)現(xiàn)類作為參數(shù),并且在testB()方法中代用callback的doSomething()方法,這個(gè)方法為CallBack接口的實(shí)現(xiàn)類doSomething()方法,在之前實(shí)現(xiàn)接口的時(shí)候根據(jù)自己的需要自定義功能代碼,而公共的代碼部分放在testB()方法中實(shí)現(xiàn)。
接下來我們結(jié)合前一節(jié)模板模式的代碼進(jìn)行修改,采用回調(diào)的方式來實(shí)現(xiàn):
首先,我們定義一個(gè)回調(diào)接口:
public interface StatementCallback {
Object doInStatement(Statement stmt) throws SQLException;
}
接下來我們重新定義JDBCtemplate類:
public class JdbcTemplate {
public final Object execute(StatementCallback action) throws SQLException{
Connection con = HsqldbUtil.getConnection();
Statement stmt = null;
try {
stmt = con.createStatement();
Object result = action.doInStatement(stmt);
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();
}
}
}
//這是是為了看著更整齊添加的方法
public Object query(StatementCallback stmt) throws SQLException{
return execute(stmt);
}
}
至此,我們已經(jīng)成功了將模板模式改為了采用回調(diào)模式。接下來我們創(chuàng)建一個(gè)test.java來測試一下,在前面我們也說過,回調(diào)模式有兩種調(diào)用方式,我們可以使用內(nèi)部類或匿名內(nèi)部類來實(shí)現(xiàn)回調(diào)方法:
內(nèi)部類:
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback {
public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>();
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;
}
}
JdbcTemplate jt = new JdbcTemplate();
return jt.query(new QueryStatementCallback());
}
在調(diào)用jdbcTemplate.query()方法時(shí),將內(nèi)部類StatementCallBack()的實(shí)例傳過去。
匿名內(nèi)部類:
//匿名類方式
public Object query2(final String sql) throws Exception{
JdbcTemplate jt = new JdbcTemplate();
return jt.query(new StatementCallback() {
public Object doInStatement(Statement stmt) throws SQLException {
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<User>();
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;
}
});
}
在平時(shí)的代碼中,我們使用匿名內(nèi)部類的方式也更加多一些。
綜上兩種回調(diào)方式可以發(fā)現(xiàn),回調(diào)模式相比于模板模式更簡潔也更靈活一些。
通過對比模板模式與回調(diào)模式我們可以發(fā)現(xiàn)模板模式有一些弊端:
當(dāng)流程中包含抽象函數(shù),子類繼承父類并實(shí)現(xiàn)父類的抽象函數(shù),這樣父類的流程這個(gè)流程是不變的,變的只是子類的抽象方法的實(shí)現(xiàn)。但是這個(gè)的基礎(chǔ)是繼承,如果你變化的部分太多,你要實(shí)現(xiàn)很多很多類,而且如果父類的流程有多個(gè),那子類要實(shí)現(xiàn)自己并不需要的抽象函數(shù),這是一個(gè)弊端。
這也是為什么spring不單一的使用傳統(tǒng)的模板方法,而加之以Callback進(jìn)行配合的原因。
試想,如果父類中有10個(gè)抽象方法,而繼承它的所有子類則要將這10個(gè)抽象方法全部實(shí)現(xiàn),子類顯得非常臃腫。而有時(shí)候某個(gè)子類只需要定制父類中的某一個(gè)方法該怎么辦呢?這個(gè)時(shí)候就要用到Callback回調(diào)了。
在文章編寫過程中,參照了一些前人的文章:
http://www.360doc.com/content/18/0714/22/56263195_770418578.shtml
https://blog.csdn.net/zhang23242/article/details/9305925