小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Spring中模板模式和回調(diào)模式的講解

 underworld 2011-08-04
話回正轉(zhuǎn),這兩天在讀spring的jdbc模板,對(duì)Spring源碼的精妙真是佩服得五體投地,極為經(jīng)典。 
spring中真是集設(shè)計(jì)模式之大成,而且用得是爐火純青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無(wú)疑使用了模板模式,但spring并不是單純使用了模板方法,而是在此基礎(chǔ)上做了創(chuàng)新,配合callback(回調(diào))一起使用,用得極其靈活。 

OK,為了防止文章再被拍磚,我寫(xiě)得更詳細(xì)點(diǎn)吧,我們首先來(lái)回顧一下模板模式: 
所謂模板板式,就是在父類(lèi)中定義算法的主要流程,而把一些個(gè)性化的步驟延遲到子類(lèi)中去實(shí)現(xiàn),父類(lèi)始終控制著整個(gè)流程的主動(dòng)權(quán),子類(lèi)只是輔助父類(lèi)實(shí)現(xiàn)某些可定制的步驟。 

有些抽象??? 
好吧,我們用代碼來(lái)說(shuō)話吧: 
首先,父類(lèi)要是個(gè)抽象類(lèi): 
Java代碼  收藏代碼
  1. public abstract class TemplatePattern {  
  2.   
  3.     //模板方法  
  4.     public final void templateMethod(){  
  5.           
  6.         method1();  
  7.         method2();//勾子方法  
  8.         method3();//抽象方法  
  9.     }  
  10.     private void method1(){  
  11.         System.out.println("父類(lèi)實(shí)現(xiàn)業(yè)務(wù)邏輯");  
  12.     }  
  13.     public void method2(){  
  14.         System.out.println("父類(lèi)默認(rèn)實(shí)現(xiàn),子類(lèi)可覆蓋");  
  15.     }  
  16.     protected abstract void method3();//子類(lèi)負(fù)責(zé)實(shí)現(xiàn)業(yè)務(wù)邏輯  
  17. }  


父類(lèi)中有三個(gè)方法,分別是method1(),method2()和method3()。 
method1()是私有方法,有且只能由父類(lèi)實(shí)現(xiàn)邏輯,由于方法是private的,所以只能父類(lèi)調(diào)用。 
method2()是所謂的勾子方法。父類(lèi)提供默認(rèn)實(shí)現(xiàn),如果子類(lèi)覺(jué)得有必要定制,則可以覆蓋父類(lèi)的默認(rèn)實(shí)現(xiàn)。 
method3()是子類(lèi)必須實(shí)現(xiàn)的方法,即制定的步驟。 
由此可看出,算法的流程執(zhí)行順序是由父類(lèi)掌控的,子類(lèi)只能配合。 

下面我們來(lái)寫(xiě)第一個(gè)子類(lèi): 
Java代碼  收藏代碼
  1. public class TemplatePatternImpl extends TemplatePattern {  
  2.   
  3.     @Override  
  4.     protected void method3() {  
  5.         System.out.println("method3()在子類(lèi)TemplatePatternImpl中實(shí)現(xiàn)了??!");  
  6.   
  7.     }  
  8.   
  9. }  

這個(gè)子類(lèi)只覆蓋了必須覆蓋的方法,我們來(lái)測(cè)試一下: 
Java代碼  收藏代碼
  1. TemplatePattern t1 = new TemplatePatternImpl();  
  2. t1.templateMethod();  

在控制臺(tái)中我們可以看到: 
Java代碼  收藏代碼
  1. 父類(lèi)實(shí)現(xiàn)業(yè)務(wù)邏輯  
  2. 父類(lèi)默認(rèn)實(shí)現(xiàn),子類(lèi)可覆蓋  
  3. method3()在子類(lèi)TemplatePatternImpl中實(shí)現(xiàn)了??!  


OK,我們來(lái)看看勾子方法的使用: 
定義第2個(gè)子類(lèi),實(shí)現(xiàn)勾子方法: 
Java代碼  收藏代碼
  1. public class TemplatePatternImpl2 extends TemplatePattern {  
  2.   
  3.     @Override  
  4.     protected void method3() {  
  5.         System.out.println("method3()在子類(lèi)TemplatePatternImpl2中實(shí)現(xiàn)了??!");  
  6.   
  7.     }  
  8.   
  9.     /* (non-Javadoc) 
  10.      * @see com.jak.pattern.template.example.TemplatePattern#method2() 
  11.      */  
  12.     @Override  
  13.     public void method2() {  
  14.         System.out.println("子類(lèi)TemplatePatternImpl2覆蓋了父類(lèi)的method2()方法??!");  
  15.     }  
  16.       
  17. }  


來(lái)測(cè)試一下: 
Java代碼  收藏代碼
  1. TemplatePattern t2 = new TemplatePatternImpl2();  
  2. t2.templateMethod();  

我們看控制臺(tái): 
Java代碼  收藏代碼
  1. 父類(lèi)實(shí)現(xiàn)業(yè)務(wù)邏輯  
  2. 子類(lèi)TemplatePatternImpl2覆蓋了父類(lèi)的method2()方法!!  
  3. method3()在子類(lèi)TemplatePatternImpl2中實(shí)現(xiàn)了??!  


OK,經(jīng)典的模板模式回顧完了(大家不要拍磚哦~~~~~~~~~~) 

接下來(lái),我們回到正題,自己模仿spring動(dòng)手寫(xiě)一個(gè)基于模板模式和回調(diào)的jdbcTemplate。 

回顧一下,spring為什么要封裝JDBC API,對(duì)外提供jdbcTemplate呢(不要仍雞蛋?。ぁ?¥#%) 
話說(shuō)SUN的JDBC API也算是經(jīng)典了,曾經(jīng)在某個(gè)年代折服了一批人。但隨著歷史的發(fā)展,純粹的JDBC API已經(jīng)過(guò)于底層,而且不易控制,由開(kāi)發(fā)人員直接接觸JDBC API,會(huì)造成不可預(yù)知的風(fēng)險(xiǎn)。還有,數(shù)據(jù)連接緩存池的發(fā)展,也不可能讓開(kāi)發(fā)人員去手工獲取JDBC了。 

好了,我們來(lái)看一段曾經(jīng)堪稱經(jīng)典的JDBC API代碼吧: 
Java代碼  收藏代碼
  1. public List<User> query() {  
  2.   
  3.     List<User> userList = new ArrayList<User>();  
  4.     String sql = "select * from User";  
  5.   
  6.     Connection con = null;  
  7.     PreparedStatement pst = null;  
  8.     ResultSet rs = null;  
  9.     try {  
  10.         con = HsqldbUtil.getConnection();  
  11.         pst = con.prepareStatement(sql);  
  12.         rs = pst.executeQuery();  
  13.   
  14.         User user = null;  
  15.         while (rs.next()) {  
  16.   
  17.             user = new User();  
  18.             user.setId(rs.getInt("id"));  
  19.             user.setUserName(rs.getString("user_name"));  
  20.             user.setBirth(rs.getDate("birth"));  
  21.             user.setCreateDate(rs.getDate("create_date"));  
  22.             userList.add(user);  
  23.         }  
  24.   
  25.   
  26.     } catch (SQLException e) {  
  27.         e.printStackTrace();  
  28.     }finally{  
  29.         if(rs != null){  
  30.             try {  
  31.                 rs.close();  
  32.             } catch (SQLException e) {  
  33.                 e.printStackTrace();  
  34.             }  
  35.         }  
  36.         try {  
  37.             pst.close();  
  38.         } catch (SQLException e) {  
  39.             e.printStackTrace();  
  40.         }  
  41.         try {  
  42.             if(!con.isClosed()){  
  43.                 try {  
  44.                     con.close();  
  45.                 } catch (SQLException e) {  
  46.                     e.printStackTrace();  
  47.                 }  
  48.             }  
  49.         } catch (SQLException e) {  
  50.             e.printStackTrace();  
  51.         }  
  52.           
  53.     }  
  54.     return userList;  
  55. }  


上面的代碼要若干年前可能是一段十分經(jīng)典的,還可能被作為example被推廣。但時(shí)過(guò)境遷,倘若哪位程序員現(xiàn)在再在自己的程序中出現(xiàn)以上代碼,不是說(shuō)明該公司的開(kāi)發(fā)框架管理混亂,就說(shuō)明這位程序員水平太“高”了。 
我們?cè)囅耄粋€(gè)簡(jiǎn)單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來(lái)梳理一下: 
1、獲取connection 
2、獲取statement 
3、獲取resultset 
4、遍歷resultset并封裝成集合 
5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常 
6、..... 
啊~~~~ 我快要暈了,在面向?qū)ο缶幊痰哪甏?,這樣的代碼簡(jiǎn)直不能上人容忍。試想,上面我們只是做了一張表的查詢,如果我們要做第2張表,第3張表呢,又是一堆重復(fù)的代碼: 
1、獲取connection 
2、獲取statement 
3、獲取resultset 
4、遍歷resultset并封裝成集合 
5、依次關(guān)閉connection,statement,resultset,而且還要考慮各種異常 
6、..... 

這時(shí)候,使用模板模式的時(shí)機(jī)到了!??! 

通過(guò)觀察我們發(fā)現(xiàn)上面步驟中大多數(shù)都是重復(fù)的,可復(fù)用的,只有在遍歷ResultSet并封裝成集合的這一步驟是可定制的,因?yàn)槊繌埍矶加成洳煌膉ava bean。這部分代碼是沒(méi)有辦法復(fù)用的,只能定制。那就讓我們用一個(gè)抽象的父類(lèi)把它們封裝一下吧: 
Java代碼  收藏代碼
  1. public abstract class JdbcTemplate {  
  2.   
  3.     //template method  
  4.     public final Object execute(String sql) throws SQLException{  
  5.           
  6.         Connection con = HsqldbUtil.getConnection();  
  7.         Statement stmt = null;  
  8.         try {  
  9.    
  10.             stmt = con.createStatement();  
  11.             ResultSet rs = stmt.executeQuery(sql);  
  12.             Object result = doInStatement(rs);//abstract method   
  13.             return result;  
  14.         }  
  15.         catch (SQLException ex) {  
  16.              ex.printStackTrace();  
  17.              throw ex;  
  18.         }  
  19.         finally {  
  20.    
  21.             try {  
  22.                 stmt.close();  
  23.             } catch (SQLException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.             try {  
  27.                 if(!con.isClosed()){  
  28.                     try {  
  29.                         con.close();  
  30.                     } catch (SQLException e) {  
  31.                         e.printStackTrace();  
  32.                     }  
  33.                 }  
  34.             } catch (SQLException e) {  
  35.                 e.printStackTrace();  
  36.             }  
  37.               
  38.         }  
  39.     }  
  40.       
  41.     //implements in subclass  
  42.     protected abstract Object doInStatement(ResultSet rs);  
  43. }  

在上面這個(gè)抽象類(lèi)中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類(lèi)負(fù)責(zé)實(shí)現(xiàn)。 
好,我們來(lái)定義一個(gè)子類(lèi),并繼承上面的父類(lèi): 
Java代碼  收藏代碼
  1. public class JdbcTemplateUserImpl extends JdbcTemplate {  
  2.   
  3.     @Override  
  4.     protected Object doInStatement(ResultSet rs) {  
  5.         List<User> userList = new ArrayList<User>();  
  6.           
  7.         try {  
  8.             User user = null;  
  9.             while (rs.next()) {  
  10.   
  11.                 user = new User();  
  12.                 user.setId(rs.getInt("id"));  
  13.                 user.setUserName(rs.getString("user_name"));  
  14.                 user.setBirth(rs.getDate("birth"));  
  15.                 user.setCreateDate(rs.getDate("create_date"));  
  16.                 userList.add(user);  
  17.             }  
  18.             return userList;  
  19.         } catch (SQLException e) {  
  20.             e.printStackTrace();  
  21.             return null;  
  22.         }  
  23.     }  
  24.   
  25. }  

由代碼可見(jiàn),我們?cè)赿oInStatement()方法中,對(duì)ResultSet進(jìn)行了遍歷,最后并返回。 
有人可能要問(wèn):我如何獲取ResultSet 并傳給doInStatement()方法?。??呵呵,問(wèn)這個(gè)問(wèn)題的大多是新手。因?yàn)榇朔椒ú皇怯勺宇?lèi)調(diào)用的,而是由父類(lèi)調(diào)用,并把ResultSet傳遞給子類(lèi)的。我們來(lái)看一下測(cè)試代碼: 
Java代碼  收藏代碼
  1. String sql = "select * from User";  
  2. JdbcTemplate jt = new JdbcTemplateUserImpl();  
  3. List<User> userList = (List<User>) jt.execute(sql);  


就是這么簡(jiǎn)單?。?nbsp;

文章至此仿佛告一段落,莫急!不防讓我們更深入一些... 

試想,如果我每次用jdbcTemplate時(shí),都要繼承一下上面的父類(lèi),是不是有些不方面呢? 
那就讓我們甩掉abstract這頂帽子吧,這時(shí),就該callback(回調(diào))上場(chǎng)了 


所謂回調(diào),就是方法參數(shù)中傳遞一個(gè)接口,父類(lèi)在調(diào)用此方法時(shí),必須調(diào)用方法中傳遞的接口的實(shí)現(xiàn)類(lèi)。 

那我們就來(lái)把上面的代碼改造一下,改用回調(diào)實(shí)現(xiàn)吧: 

首先,我們來(lái)定義一個(gè)回調(diào)接口: 
Java代碼  收藏代碼
  1. public interface StatementCallback {  
  2.     Object doInStatement(Statement stmt) throws SQLException;  
  3. }  


這時(shí)候,我們就要方法的簽名改一下了: 
Java代碼  收藏代碼
  1. private final Object execute(StatementCallback action) throws SQLException  


里面的獲取數(shù)據(jù)方式也要做如下修改: 
Java代碼  收藏代碼
  1. Object result = action.doInStatement(stmt);//abstract method   


為了看著順眼,我們來(lái)給他封裝一層吧: 
Java代碼  收藏代碼
  1. public Object query(StatementCallback stmt) throws SQLException{  
  2.     return execute(stmt);  
  3. }  


OK,大功告成! 
我們來(lái)寫(xiě)一個(gè)測(cè)試類(lèi)Test.java測(cè)試一下吧: 
這時(shí)候,訪問(wèn)有兩種方式,一種是內(nèi)部類(lèi)的方式,一種是匿名方式。 

先來(lái)看看內(nèi)部類(lèi)的方式: 
Java代碼  收藏代碼
  1. //內(nèi)部類(lèi)方式  
  2.     public Object query(final String sql) throws SQLException {  
  3.         class QueryStatementCallback implements StatementCallback {  
  4.   
  5.             public Object doInStatement(Statement stmt) throws SQLException {  
  6.                 ResultSet rs = stmt.executeQuery(sql);  
  7.                 List<User> userList = new ArrayList<User>();  
  8.   
  9.                 User user = null;  
  10.                 while (rs.next()) {  
  11.   
  12.                     user = new User();  
  13.                     user.setId(rs.getInt("id"));  
  14.                     user.setUserName(rs.getString("user_name"));  
  15.                     user.setBirth(rs.getDate("birth"));  
  16.                     user.setCreateDate(rs.getDate("create_date"));  
  17.                     userList.add(user);  
  18.                 }  
  19.                 return userList;  
  20.   
  21.             }  
  22.   
  23.         }  
  24.   
  25.         JdbcTemplate jt = new JdbcTemplate();  
  26.         return jt.query(new QueryStatementCallback());  
  27.     }  


在調(diào)用jdbcTemplate.query()方法時(shí),傳一個(gè)StatementCallBack()的實(shí)例過(guò)去,也就是我們的內(nèi)部類(lèi)。 

再來(lái)看看匿名方式: 
Java代碼  收藏代碼
  1. //匿名類(lèi)方式  
  2.     public Object query2(final String sql) throws Exception{  
  3.           
  4.         JdbcTemplate jt = new JdbcTemplate();  
  5.         return jt.query(new StatementCallback() {  
  6.               
  7.             public Object doInStatement(Statement stmt) throws SQLException {  
  8.                 ResultSet rs = stmt.executeQuery(sql);  
  9.                 List<User> userList = new ArrayList<User>();  
  10.   
  11.                 User user = null;  
  12.                 while (rs.next()) {  
  13.   
  14.                     user = new User();  
  15.                     user.setId(rs.getInt("id"));  
  16.                     user.setUserName(rs.getString("user_name"));  
  17.                     user.setBirth(rs.getDate("birth"));  
  18.                     user.setCreateDate(rs.getDate("create_date"));  
  19.                     userList.add(user);  
  20.                 }  
  21.                 return userList;  
  22.   
  23.             }  
  24.         });  
  25.           
  26.     }  

相比之下,這種方法更為簡(jiǎn)潔。 
為什么spring不用傳統(tǒng)的模板方法,而加之以Callback進(jìn)行配合呢? 
試想,如果父類(lèi)中有10個(gè)抽象方法,而繼承它的所有子類(lèi)則要將這10個(gè)抽象方法全部實(shí)現(xiàn),子類(lèi)顯得非常臃腫。而有時(shí)候某個(gè)子類(lèi)只需要定制父類(lèi)中的某一個(gè)方法該怎么辦呢?這個(gè)時(shí)候就要用到Callback回調(diào)了。 

離spring jdbcTemplate再近一點(diǎn) 
上面這種方式基本上實(shí)現(xiàn)了模板方法+回調(diào)模式。但離spring的jdbcTemplate還有些距離。 
我們可以再深入一些。。。 

我們上面雖然實(shí)現(xiàn)了模板方法+回調(diào)模式,但相對(duì)于Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 
RowMapper接口負(fù)責(zé)處理某一行的數(shù)據(jù),例如,我們可以在mapRow方法里對(duì)某一行記錄進(jìn)行操作,或封裝成entity。 
ResultSetExtractor是數(shù)據(jù)集抽取器,負(fù)責(zé)遍歷ResultSet并根據(jù)RowMapper里的規(guī)則對(duì)數(shù)據(jù)進(jìn)行處理。 
RowMapper和ResultSetExtractor區(qū)別是,RowMapper是處理某一行數(shù)據(jù),返回一個(gè)實(shí)體對(duì)象。而ResultSetExtractor是處理一個(gè)數(shù)據(jù)集合,返回一個(gè)對(duì)象集合。 

當(dāng)然,上面所述僅僅是Spring JdbcTemplte實(shí)現(xiàn)的基本原理,Spring JdbcTemplate內(nèi)部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內(nèi),以及采用JdbcAccessor來(lái)管理DataSource和轉(zhuǎn)換異常等。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多