Java設(shè)計模式--模板模式

一.模板模式概念

在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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容