【行為型模式十六】模板方法模式-2(Template Method)

3.4 典型應(yīng)用:排序##

模板方法模式的一個(gè)非常典型的應(yīng)用,就是實(shí)現(xiàn)排序的功能。至于有些朋友認(rèn)為排序是策略模式的體現(xiàn),這很值得商榷。先來看看在Java中排序功能的實(shí)現(xiàn),然后再來說明為什么排序的實(shí)現(xiàn)主要體現(xiàn)了模板方法模式,而非策略模式。

在java.util包中,有一個(gè)Collections類,它里面實(shí)現(xiàn)了對(duì)列表排序的功能,它提供了一個(gè)靜態(tài)的sort方法,接受一個(gè)列表和一個(gè)Comparator接口的實(shí)例,這個(gè)方法實(shí)現(xiàn)的大致步驟是:

先把列表轉(zhuǎn)換成為對(duì)象數(shù)組;

通過Arrays的sort方法來對(duì)數(shù)組進(jìn)行排序,傳入Comparator接口的實(shí)例;

然后再把排好序的數(shù)組的數(shù)據(jù)設(shè)置回到原來的列表對(duì)象中去;

這其中的算法步驟是固定的,也就是算法骨架是固定的了,只是其中具體比較數(shù)據(jù)大小的步驟,需要由外部來提供,也就是傳入的Comparator接口的實(shí)例,就是用來實(shí)現(xiàn)數(shù)據(jù)比較的,在算法內(nèi)部會(huì)通過這個(gè)接口來回調(diào)具體的實(shí)現(xiàn)。

如果Comparator接口的compare()方法返回一個(gè)小于0的數(shù),表示被比較的兩個(gè)對(duì)象中,前面的對(duì)象小于后面的對(duì)象;如果返回一個(gè)等于0的數(shù),表示被比較的兩個(gè)對(duì)象相等;如果
返回一個(gè)大于0的數(shù),表示被比較的兩個(gè)對(duì)象中,前面的對(duì)象大于后面的對(duì)象。

下面一起看看使用Collections來對(duì)列表排序的例子,假如現(xiàn)在要實(shí)現(xiàn)對(duì)一個(gè)擁有多個(gè)用戶數(shù)據(jù)模型的列表進(jìn)行排序。

  1. 當(dāng)然,先要定義出封裝用戶數(shù)據(jù)的對(duì)象模型來,示例代碼如下:
/**
 * 用戶數(shù)據(jù)模型
 */
public class UserModel {
    private String userId,name;
    private int age;
    public UserModel(String userId,String name,int age) {
        this.userId = userId;
        this.name = name;
        this.age = age;
    }
    public String getUserId() {
        return userId;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public String toString(){
        return "userId="+userId+",name="+name+",age="+age;
    }
}
  1. 直接使用Collections來排序,寫個(gè)客戶端來測(cè)試一下,示例代碼如下:
public class Client {
    public static void main(String[] args) {
        //準(zhǔn)備要測(cè)試的數(shù)據(jù)
        UserModel um1 = new UserModel("u1","user1",23);
        UserModel um2 = new UserModel("u2","user2",22);
        UserModel um3 = new UserModel("u3","user3",21);
        UserModel um4 = new UserModel("u4","user4",24);
        //添加到列表中
        List<UserModel> list = new ArrayList<UserModel>();
        list.add(um1);
        list.add(um2);
        list.add(um3);
        list.add(um4);
     
        System.out.println("排序前---------------------〉");
        printList(list);
        //實(shí)現(xiàn)比較器,也可以單獨(dú)用一個(gè)類來實(shí)現(xiàn)
        Comparator c = new Comparator(){
            public int compare(Object obj1, Object obj2) {
                //假如實(shí)現(xiàn)按照年齡升序排序
                UserModel tempUm1 = (UserModel)obj1;
                UserModel tempUm2 = (UserModel)obj2;
                if(tempUm1.getAge() > tempUm2.getAge()){
                    return 1;
                }else if(tempUm1.getAge() == tempUm2.getAge()){
                    return 0;
                }else if(tempUm1.getAge() < tempUm2.getAge()){
                    return -1;
                }
                return 0;
            }
        };
     
        //排序 
        Collections.sort(list,c);
     
        System.out.println("排序后---------------------〉");
        printList(list);
    }
    private static void printList(List<UserModel> list){
        for(UserModel um : list){
            System.out.println(um);
        }
    }
}

運(yùn)行一下,結(jié)果如下所示:

排序前---------------------〉
userId=u1,name=user1,age=23
userId=u2,name=user2,age=22
userId=u3,name=user3,age=21
userId=u4,name=user4,age=24
排序后---------------------〉
userId=u3,name=user3,age=21
userId=u2,name=user2,age=22
userId=u1,name=user1,age=23
userId=u4,name=user4,age=24
  1. 小結(jié)一下

看了上面的示例,你會(huì)發(fā)現(xiàn),究竟列表會(huì)按照什么標(biāo)準(zhǔn)來排序,完全是依靠Comparator的具體實(shí)現(xiàn),上面實(shí)現(xiàn)的是按照年齡升序排列,你可以嘗試修改這個(gè)排序的比較器,那么得到的結(jié)果就會(huì)不一樣了。

也就是說,排序的算法是已經(jīng)固定了的,只是進(jìn)行排序比較的這一個(gè)步驟,由外部來實(shí)現(xiàn),我們就可以通過修改這個(gè)步驟的實(shí)現(xiàn),從而實(shí)現(xiàn)不同的排序方式。因此從排序比較這個(gè)功能來看,是策略模式的體現(xiàn)。

但是請(qǐng)注意一點(diǎn),你只是修改的排序的比較方式,并不是修改了整個(gè)排序的算法,事實(shí)上,現(xiàn)在Collections的sort()方法使用的是合并排序的算法,無論你怎么修改比較器的實(shí)現(xiàn),sort()方法實(shí)現(xiàn)的算法是不會(huì)改變的,不可能變成了冒泡排序或是其它的排序算法。

  1. 排序,到底是模板方法模式的實(shí)例,還是策略模式的實(shí)例,到底哪個(gè)說法更合適?

認(rèn)為是策略模式的實(shí)例的理由:

首先上面的排序?qū)崿F(xiàn),并沒有如同標(biāo)準(zhǔn)的模板方法模式那樣,使用子類來擴(kuò)展父類,至少從表面上看不太像模板方法模式;

其次排序使用的Comparator的實(shí)例,可以看成是不同的算法實(shí)現(xiàn),在具體排序時(shí),會(huì)選擇使用不同的Comparator實(shí)現(xiàn),就相當(dāng)于是在切換算法的實(shí)現(xiàn)。

因此認(rèn)為排序是策略模式的實(shí)例。

認(rèn)為是模板方法模式的實(shí)例的理由:

首先,模板方法模式的本質(zhì)是固定算法骨架,雖然使用繼承是標(biāo)準(zhǔn)的實(shí)現(xiàn)方式,但是通過回調(diào)來實(shí)現(xiàn),也不能說這就不是模板方法模式;

其次,從整體程序上看,排序的算法并沒有改變,不過是某些步驟的實(shí)現(xiàn)發(fā)生了變化,也就是說通過Comparator來切換的是不同的比較大小的實(shí)現(xiàn),相對(duì)于整個(gè)排序算法而言,它不過是其中的一個(gè)步驟而已。

因此認(rèn)為是模板方法模式的實(shí)例。

  1. 總結(jié)語:

排序的實(shí)現(xiàn),實(shí)際上組合使用了模板方法模式和策略模式,從整體來看是模板方法模式,但到了局部,比如排序比較算法的實(shí)現(xiàn)上,就是使用的策略模式了。

至于排序具體屬于誰的實(shí)例,這或許是個(gè)仁者見仁、智者見智的事情,我們也不想做老好人,我們傾向于說:排序是模板方法模式的實(shí)例。畢竟設(shè)計(jì)模式的東西,要從整體上、設(shè)計(jì)上、從本質(zhì)上去看待問題,而不能從表面上或者是局部來看待問題

3.5 實(shí)現(xiàn)通用增刪改查##

對(duì)于實(shí)現(xiàn)通用的增刪改查的功能,基本上是每個(gè)做企業(yè)級(jí)應(yīng)用系統(tǒng)的公司都有的功能,實(shí)現(xiàn)的方式也是多種多樣,一種很常見的設(shè)計(jì)就是泛型加上模板方法模式,再加上使用Java回調(diào)技術(shù),尤其是在使用Spring和Hibernate等流行框架的應(yīng)用系統(tǒng)中很是常見

為了突出主題,以免分散大家的注意力,我們不去使用Spring和Hibernate這樣的流行框架,也不去使用泛型,只用模板方法模式來實(shí)現(xiàn)一個(gè)簡單的、用JDBC實(shí)現(xiàn)的通用增刪改查的功能。

先在數(shù)據(jù)庫中定義一個(gè)演示用的表,演示用的是Oracle數(shù)據(jù)庫,其實(shí)你可以用任意的數(shù)據(jù)庫,只是數(shù)據(jù)類型要做相應(yīng)的調(diào)整,簡單的數(shù)據(jù)字典如下:表名是tbl_user

Paste_Image.png
  1. 定義相應(yīng)的數(shù)據(jù)對(duì)象來描述數(shù)據(jù),示例代碼如下:
/**
 * 描述用戶的數(shù)據(jù)模型
 */
public class UserModel {
    private String uuid;
    private String name;
    private int age;
    public String getUuid() {
        return uuid;
    }
    public void setUuid(String uuid) {
        this.uuid = uuid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }  
    public String toString(){
        return "uuid="+uuid+",name="+name+",age="+age;
    }
}
  1. 定義一個(gè)用于封裝通用查詢數(shù)據(jù)的查詢用的數(shù)據(jù)模型,由于這個(gè)查詢數(shù)據(jù)模型和上面定義的數(shù)據(jù)模型有很大一部分是相同的,因此讓這個(gè)查詢模型繼承上面的數(shù)據(jù)模型,然后添加上多出來的查詢條件。示例代碼如下:
/**
 * 描述查詢用戶的條件數(shù)據(jù)的模型
 */
public class UserQueryModel extends UserModel{
    /**
     * 年齡是一個(gè)區(qū)間查詢,也就是年齡查詢的條件可以是:
     * age >= 條件值1  and  age <= 條件值2
     * 把UserModel中的age當(dāng)作條件值1,
     * 這里定義的age2當(dāng)作條件值2
     */
    private int age2;
    public int getAge2() {
        return age2;
    }
    public void setAge2(int age2) {
        this.age2 = age2;
    }
}
  1. 為了讓大家能更好的理解這個(gè)通用的實(shí)現(xiàn),先不去使用模板方法模式,直接使用JDBC來實(shí)現(xiàn)增刪改查的功能。

所有的方法都需要和數(shù)據(jù)庫進(jìn)行連接,因此先把和數(shù)據(jù)庫連接的公共方法定義出來,沒有使用連接池,用最簡單的JDBC自己連接,示例代碼如下:

/**
 * 獲取與數(shù)據(jù)庫的連接
 * @return 數(shù)據(jù)庫連接
 * @throws Exception
 */
private Connection getConnection()throws Exception{
    Class.forName("你用的數(shù)據(jù)庫對(duì)應(yīng)的JDBC驅(qū)動(dòng)類");
    return DriverManager.getConnection(
             "連接數(shù)據(jù)庫的URL",
             "用戶名","密碼");
}

使用純JDBC來實(shí)現(xiàn)新增的功能,示例代碼如下:

public void create(UserModel um) {
    Connection conn = null;
    try {
        conn = this.getConnection();
        String sql = "insert into tbl_user values(?,?,?)";
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setString(1, um.getUuid());
        pstmt.setString(2, um.getName());
        pstmt.setInt(3, um.getAge());

        pstmt.executeUpdate();

        pstmt.close();
    } catch (Exception err) {
        err.printStackTrace();
    } finally {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

修改和刪除的功能跟新增差不多,只是sql不同,還有設(shè)置sql中變量值不同,這里就不去寫了。

接下來看看查詢方面的功能,查詢方面只做一個(gè)通用的查詢實(shí)現(xiàn),其它查詢的實(shí)現(xiàn)基本上也差不多,示例代碼如下:

public Collection getByCondition(UserQueryModel uqm){
    Collection col = new ArrayList();
    Connection conn = null;
    try{
        conn = this.getConnection();
        String sql = "select * from tbl_user where 1=1 ";
        sql = this.prepareSql(sql, uqm);

        PreparedStatement pstmt = conn.prepareStatement(sql);
        this.setValue(pstmt, uqm);
        ResultSet rs = pstmt.executeQuery();
        while(rs.next()){
            col.add(this.rs2Object(rs));
        }

        rs.close();
        pstmt.close();
    }catch(Exception err){
        err.printStackTrace();
    }finally{
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    return col;
}

/**
 * 為通用查詢動(dòng)態(tài)的拼接sql的條件部分,基本思路是:
 * 如果用戶填寫了相應(yīng)的條件,那么才在sql中添加對(duì)應(yīng)的條件
 * @param sql sql的主干部分
 * @param uqm 封裝查詢條件的數(shù)據(jù)模型
 * @return 拼接好的sql語句
 */
private String prepareSql(String sql,UserQueryModel uqm){
    StringBuffer buffer = new StringBuffer();
    buffer.append(sql);
    //絕對(duì)匹配
    if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
        buffer.append(" and uuid=? ");
    }
    //模糊匹配
    if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
        buffer.append(" and name like ? ");
    }
    //區(qū)間匹配
    if(uqm.getAge() > 0){
        buffer.append(" and age >=? ");
    }
    if(uqm.getAge2() > 0){
        buffer.append(" and age <=? ");
    }
    return buffer.toString();
}

/**
 * 為通用查詢的sql動(dòng)態(tài)設(shè)置條件的值
 * @param pstmt 預(yù)處理查詢sql的對(duì)象
 * @param uqm 封裝查詢條件的數(shù)據(jù)模型
 * @throws Exception
 */
private void setValue(PreparedStatement pstmt,UserQueryModel uqm)throws Exception{
    int count = 1;
    if(uqm.getUuid()!=null && uqm.getUuid().trim().length()>0){
        pstmt.setString(count, uqm.getUuid());
        count++;
    }
    if(uqm.getName()!=null && uqm.getName().trim().length()>0){
        pstmt.setString(count, "%"+uqm.getName()+"%");
        count++;
    }
    if(uqm.getAge() > 0){
        pstmt.setInt(count, uqm.getAge());
        count++;
    }
    if(uqm.getAge2() > 0){
        pstmt.setInt(count, uqm.getAge2());
        count++;
    }
}

/**
 * 把查詢返回的結(jié)果集轉(zhuǎn)換成為對(duì)象
 * @param rs 查詢返回的結(jié)果集
 * @return 查詢返回的結(jié)果集轉(zhuǎn)換成為對(duì)象
 * @throws Exception
 */
private UserModel rs2Object(ResultSet rs)throws Exception{
    UserModel um = new UserModel();
    String uuid = rs.getString("uuid");
    String name = rs.getString("name");
    int age = rs.getInt("age");
     
    um.setAge(age);
    um.setName(name);
    um.setUuid(uuid);
     
    return um;
}
  1. 基本的JDBC實(shí)現(xiàn)寫完了,該來看看如何把模板方法模式用上了。模板方法是要定義算法的骨架,而具體步驟的實(shí)現(xiàn)還是由子類來完成,因此把固定的算法骨架抽取出來,就成了使用模板方法模式的重點(diǎn)了

首先來觀察新增、修改、刪除的功能,發(fā)現(xiàn)哪些是固定的,哪些是變化的呢?分析發(fā)現(xiàn)變化的只有Sql語句,還有為Sql中的“?”設(shè)置值的語句,真正執(zhí)行sql的過程是差不多的,是不變化的。

再來觀察查詢的方法,查詢的過程是固定的,變化的除了有Sql語句、為Sql中的“?”設(shè)置值的語句之外,還多了一個(gè)如何把查詢回來的結(jié)果集轉(zhuǎn)換成對(duì)象集的實(shí)現(xiàn)。

好了,找到變與不變之處,就可以來設(shè)計(jì)模板了,先定義出增刪改查各自的實(shí)現(xiàn)步驟來,也就是定義好各自的算法骨架,然后把變化的部分定義成為原語操作或鉤子操作如果一定要子類實(shí)現(xiàn)的那就定義成為原語操作;在模板中提供默認(rèn)實(shí)現(xiàn),且不強(qiáng)制子類實(shí)現(xiàn)的功能定義成為鉤子操作就可以了

另外,來回需要傳遞數(shù)據(jù),由于是通用的方法,就不能用具體的類型了,又不考慮泛型,那么就定義成Object類型好了。

根據(jù)上面的思路,一個(gè)簡單的、能實(shí)現(xiàn)對(duì)數(shù)據(jù)進(jìn)行增刪改查的模板就可以實(shí)現(xiàn)出來了,完整的示例代碼如下:

/**
 * 一個(gè)簡單的實(shí)現(xiàn)JDBC增刪改查功能的模板
 */
public abstract class JDBCTemplate {
    /**
     * 定義當(dāng)前的操作類型是新增
     */
    protected final static int TYPE_CREATE = 1;
    /**
     * 定義當(dāng)前的操作類型是修改
     */
    protected final static int TYPE_UPDATE = 2;
    /**
     * 定義當(dāng)前的操作類型是刪除
     */
    protected final static int TYPE_DELETE = 3;
    /**
     * 定義當(dāng)前的操作類型是按條件查詢
     */
    protected final static int TYPE_CONDITION = 4;
  
    /*---------------------模板方法---------------------*/  
    /**
     * 實(shí)現(xiàn)新增的功能
     * @param obj 需要被新增的數(shù)據(jù)對(duì)象
     */
    public final void create(Object obj){
       //1:獲取新增的sql
       String sql = this.getMainSql(TYPE_CREATE);
       //2:調(diào)用通用的更新實(shí)現(xiàn)
       this.executeUpdate(sql, TYPE_CREATE,obj);
    }
    /**
     * 實(shí)現(xiàn)修改的功能
     * @param obj 需要被修改的數(shù)據(jù)對(duì)象
     */
    public final void update(Object obj){
       //1:獲取修改的sql
       String sql = this.getMainSql(TYPE_UPDATE);
       //2:調(diào)用通用的更新實(shí)現(xiàn)
       this.executeUpdate(sql, TYPE_UPDATE,obj);
    }
    /**
     * 實(shí)現(xiàn)刪除的功能
     * @param obj 需要被刪除的數(shù)據(jù)對(duì)象
     */
    public final void delete(Object obj){
       //1:獲取刪除的sql
       String sql = this.getMainSql(TYPE_DELETE);
       //2:調(diào)用通用的更新實(shí)現(xiàn)
       this.executeUpdate(sql, TYPE_DELETE,obj);
    }
    /**
     * 實(shí)現(xiàn)按照條件查詢的功能
     * @param qm 封裝查詢條件的數(shù)據(jù)對(duì)象
     * @return 符合條件的數(shù)據(jù)對(duì)象集合
     */
    public final Collection getByCondition(Object qm){
       //1:獲取查詢的sql
       String sql = this.getMainSql(TYPE_CONDITION);
       //2:調(diào)用通用的查詢實(shí)現(xiàn)
       return this.getByCondition(sql, qm);
    }
  
    /*---------------------原語操作---------------------*/      
    /**
     * 獲取操作需要的主干sql
     * @param type 操作類型
     * @return 操作對(duì)應(yīng)的主干sql
     */
    protected abstract String getMainSql(int type);
    /**
     * 為更新操作的sql中的"?"設(shè)置值
     * @param type 操作類型
     * @param pstmt PreparedStatement對(duì)象
     * @param obj 操作的數(shù)據(jù)對(duì)象
     * @throws Exception
     */
    protected abstract void setUpdateSqlValue(int type, PreparedStatement pstmt,Object obj) throws Exception;
    /**
     * 為通用查詢動(dòng)態(tài)的拼接sql的條件部分,基本思路是:
     * 只有用戶填寫了相應(yīng)的條件,那么才在sql中添加對(duì)應(yīng)的條件
     * @param sql sql的主干部分
     * @param qm 封裝查詢條件的數(shù)據(jù)模型
     * @return 拼接好的sql語句
     */
    protected abstract String prepareQuerySql(String sql,Object qm);
    /**
     * 為通用查詢的sql動(dòng)態(tài)設(shè)置條件的值
     * @param pstmt 預(yù)處理查詢sql的對(duì)象
     * @param qm 封裝查詢條件的數(shù)據(jù)模型
     * @throws Exception
     */
    protected abstract void setQuerySqlValue(PreparedStatement pstmt,Object qm)throws Exception;
    /**
     * 把查詢返回的結(jié)果集轉(zhuǎn)換成為數(shù)據(jù)對(duì)象
     * @param rs 查詢返回的結(jié)果集
     * @return 查詢返回的結(jié)果集轉(zhuǎn)換成為數(shù)據(jù)對(duì)象
     * @throws Exception
     */
    protected abstract Object rs2Object(ResultSet rs)throws Exception;
  
    /*---------------------鉤子操作---------------------*/      
    /**
     * 連接數(shù)據(jù)庫的默認(rèn)實(shí)現(xiàn),可以被子類覆蓋
     * @return 數(shù)據(jù)庫連接
     * @throws Exception
     */
    protected Connection getConnection()throws Exception{
       Class.forName("你用的數(shù)據(jù)庫對(duì)應(yīng)的JDBC驅(qū)動(dòng)類");
       return DriverManager.getConnection(
             "連接數(shù)據(jù)庫的URL",
             "用戶名","密碼");
    }
    /**
     * 執(zhí)行查詢
     * @param sql 查詢的主干sql語句
     * @param qm 封裝查詢條件的數(shù)據(jù)模型
     * @return 查詢后的結(jié)果對(duì)象集合
     */
    protected Collection getByCondition(String sql,Object qm){
       Collection col = new ArrayList();
       Connection conn = null;
       try{
           //調(diào)用鉤子方法
           conn = this.getConnection();
           //調(diào)用原語操作
           sql = this.prepareQuerySql(sql, qm);
           PreparedStatement pstmt = conn.prepareStatement(sql);
           //調(diào)用原語操作
           this.setQuerySqlValue(pstmt, qm);
           ResultSet rs = pstmt.executeQuery();
           while(rs.next()){
              //調(diào)用原語操作
              col.add(this.rs2Object(rs));
           }

           rs.close();
           pstmt.close();
       }catch(Exception err){
           err.printStackTrace();
       }finally{
           try {
              conn.close();
           } catch (SQLException e) {
              e.printStackTrace();
           }
       }
       return col;
    }
    /**
     * 執(zhí)行更改數(shù)據(jù)的sql語句,包括增刪改的功能
     * @param sql 需要執(zhí)行的sql語句
     * @param callback 回調(diào)接口,回調(diào)為sql語句賦值的方法
     */
    protected void executeUpdate(String sql,int type,Object obj){
       Connection conn = null;
       try{
           //調(diào)用鉤子方法        
           conn = this.getConnection();
           PreparedStatement pstmt = conn.prepareStatement(sql);
           //調(diào)用原語操作
           this.setUpdateSqlValue(type,pstmt,obj);         
           pstmt.executeUpdate();         
           pstmt.close();
       }catch(Exception err){
           err.printStackTrace();
       }finally{
           try {
              conn.close();
           } catch (SQLException e) {
              e.printStackTrace();
           }
       }
    }
}
  1. 簡單但是可以通用的JDBC模板做好了,看看如何使用這個(gè)模板來實(shí)現(xiàn)具體的增刪改查功能,示例代碼如下:
/**
 * 具體的實(shí)現(xiàn)用戶管理的增刪改查功能
 */
public class UserJDBC extends JDBCTemplate{  
    protected String getMainSql(int type) {
       //根據(jù)操作類型,返回相應(yīng)的主干sql語句
       String sql = "";
       if(type == TYPE_CREATE){
           sql = "insert into tbl_user values(?,?,?)";
       }else if(type == TYPE_DELETE){
           sql = "delete from tbl_user where uuid=?";
       }else if(type == TYPE_UPDATE){
           sql = "update tbl_user set name=?,age=? where uuid=?";
       }else if(type == TYPE_CONDITION){
           sql = "select * from tbl_user where 1=1 ";
       }
       return sql;
    }
    protected void setUpdateSqlValue(int type, PreparedStatement pstmt,Object obj) throws Exception{
       //設(shè)置增、刪、改操作的sql中"?"對(duì)應(yīng)的值
       if(type == TYPE_CREATE){
           this.setCreateValue(pstmt, (UserModel)obj);
       }else if(type == TYPE_DELETE){
           this.setDeleteValue(pstmt, (UserModel)obj);
       }else if(type == TYPE_UPDATE){
          this.setUpdateValue(pstmt, (UserModel)obj);
       }
    }
    protected Object rs2Object(ResultSet rs)throws Exception{
       UserModel um = new UserModel();
       String uuid = rs.getString("uuid");
       String name = rs.getString("name");
       int age = rs.getInt("age");
     
       um.setAge(age);
       um.setName(name);
       um.setUuid(uuid);
     
       return um;
    }
    protected String prepareQuerySql(String sql,Object qm){
       UserQueryModel uqm = (UserQueryModel)qm;
       StringBuffer buffer = new StringBuffer();
       buffer.append(sql);
       if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
           buffer.append(" and uuid=? ");
       }
       if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
           buffer.append(" and name like ? ");
       }
       if(uqm.getAge() > 0){
           buffer.append(" and age >=? ");
       }
       if(uqm.getAge2() > 0){
           buffer.append(" and age <=? ");
       }
       return buffer.toString();
    }
    protected void setQuerySqlValue(PreparedStatement pstmt, Object qm)throws Exception{
       UserQueryModel uqm = (UserQueryModel)qm;
       int count = 1;
       if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
           pstmt.setString(count, uqm.getUuid());
           count++;
       }
       if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
           pstmt.setString(count, "%"+uqm.getName()+"%");
           count++;
       }
       if(uqm.getAge() > 0){
           pstmt.setInt(count, uqm.getAge());
           count++;
       }
       if(uqm.getAge2() > 0){
           pstmt.setInt(count, uqm.getAge2());
           count++;
       }
    }
    private void setCreateValue(PreparedStatement pstmt,UserModel um)throws Exception{
       pstmt.setString(1, um.getUuid());
       pstmt.setString(2, um.getName());
       pstmt.setInt(3, um.getAge());
    }
    private void setUpdateValue(PreparedStatement pstmt,UserModel um)throws Exception{
       pstmt.setString(1, um.getName());
       pstmt.setInt(2, um.getAge());
       pstmt.setString(3, um.getUuid());
    }
    private void setDeleteValue(PreparedStatement pstmt,UserModel um)throws Exception{
       pstmt.setString(1, um.getUuid());
    }
}

看到這里,可能有些朋友會(huì)想,為何不把準(zhǔn)備sql的方法、為sql中“?”賦值的方法、還有結(jié)果集映射成為對(duì)象的方法也做成公共的呢?

其實(shí)這些方法是可以考慮做成公共的,用反射機(jī)制就可以實(shí)現(xiàn),但是這里為了突出模板方法模式的使用,免得加的東西太多,把大家搞迷惑了。

事實(shí)上,用模板方法加上泛型再加上反射的技術(shù),就可以實(shí)現(xiàn)可重用的,使用模板時(shí)幾乎不用再寫代碼的數(shù)據(jù)層實(shí)現(xiàn),這里就不去展開了。

  1. 享受的時(shí)刻到了,來寫個(gè)客戶端,使用UserJDBC的實(shí)現(xiàn),示例代碼如下:
public class Client {
    public static void main(String[] args) {
       UserJDBC uj = new UserJDBC();
       //先新增幾條
       UserModel um1 = new UserModel();
       um1.setUuid("u1");
       um1.setName("張三");
       um1.setAge(22);     
       uj.create(um1);     
     
       UserModel um2 = new UserModel();
       um2.setUuid("u2");
       um2.setName("李四");
       um2.setAge(25);     
       uj.create(um2);
     
       UserModel um3 = new UserModel();
       um3.setUuid("u3");
       um3.setName("王五");
       um3.setAge(32);     
       uj.create(um3);
     
       //測(cè)試修改
       um3.setName("王五被改了");
       um3.setAge(35);
       uj.update(um3);
     
       //測(cè)試查詢
       UserQueryModel uqm = new UserQueryModel();
       uqm.setAge(20);
       uqm.setAge2(36);
       Collection<UserModel> col = uj.getByCondition(uqm);
       for(UserModel tempUm : col){
           System.out.println(tempUm);
       }
    }
}

3.6 模板方法模式的優(yōu)缺點(diǎn)##

  1. 實(shí)現(xiàn)代碼復(fù)用

模板方法模式是一種實(shí)現(xiàn)代碼復(fù)用的很好的手段。通過把子類的公共功能提煉和抽取,把公共部分放到模板里面去實(shí)現(xiàn)。

  1. 算法骨架不容易升級(jí)

模板方法模式最基本的功能就是通過模板的制定,把算法骨架完全固定下來。事實(shí)上模板和子類是非常耦合的,如果要對(duì)模板中的算法骨架進(jìn)行變更,可能就會(huì)要求所有相關(guān)的子類進(jìn)行相應(yīng)的變化。所以抽取算法骨架的時(shí)候要特別小心,盡量確保是不會(huì)變化的部分才放到模板中。

3.7 思考模板方法模式##

  1. 模板方法模式的本質(zhì)

模板方法模式的本質(zhì):固定算法骨架。

模板方法模式主要是通過制定模板,把算法步驟固定下來,至于誰來實(shí)現(xiàn),模板可以自己提供實(shí)現(xiàn),也可以由子類去實(shí)現(xiàn),還可以通過回調(diào)機(jī)制讓其它類來實(shí)現(xiàn)。

通過固定算法骨架,來約束子類的行為,并在特定的擴(kuò)展點(diǎn),來讓子類進(jìn)行功能擴(kuò)展,從而讓程序既有很好的復(fù)用性,又有較好的擴(kuò)展性。

  1. 對(duì)設(shè)計(jì)原則的體現(xiàn)

模板方法很好的體現(xiàn)了開閉原則和里氏替換原則。

首先從設(shè)計(jì)上,先分離變與不變,然后把不變的部分抽取出來,定義到父類里面,比如算法骨架,比如一些公共的、固定的實(shí)現(xiàn)等等。這些不變的部分被封閉起來,盡量不去修改它了,要擴(kuò)展新的功能,那就使用子類來擴(kuò)展,通過子類來實(shí)現(xiàn)可變化的步驟,對(duì)于這種新增功能的做法是開放的。

其次,能夠?qū)崿F(xiàn)統(tǒng)一的算法骨架,通過切換不同的具體實(shí)現(xiàn)來切換不同的功能,一個(gè)根本原因就是里氏替換原則,遵循這個(gè)原則,保證所有的子類實(shí)現(xiàn)的是同一個(gè)算法模板,并能在使用模板的地方,根據(jù)需要,切換不同的具體實(shí)現(xiàn)。

  1. 何時(shí)選用模板方法模式

建議在如下情況中,選用模板方法模式:

需要固定定義算法骨架,實(shí)現(xiàn)一個(gè)算法的不變的部分,并把可變的行為留給子類來實(shí)現(xiàn)的情況;

各個(gè)子類中具有公共行為,應(yīng)該抽取出來,集中在一個(gè)公共類中去實(shí)現(xiàn),從而避免代碼重復(fù);

需要控制子類擴(kuò)展的情況。模板方法模式會(huì)在特定的點(diǎn)來調(diào)用子類的方法,這樣只允許在這些點(diǎn)進(jìn)行擴(kuò)展;

3.8 相關(guān)模式##

  1. 模板方法模式和工廠方法模式

這兩個(gè)模式可以配合使用。

模板方法模式可以通過工廠方法來獲取需要調(diào)用的對(duì)象。

  1. 模板方法模式和策略模式

這兩個(gè)模式的功能有些相似,但是是有區(qū)別的。

從表面上看,兩個(gè)模式都能實(shí)現(xiàn)算法的封裝,但是模板方法封裝的是算法的骨架,這個(gè)算法骨架是不變的,變化的是算法中某些步驟的具體實(shí)現(xiàn);而策略模式是把某個(gè)步驟的具體實(shí)現(xiàn)算法封裝起來,所有封裝的算法對(duì)象是等價(jià)的,可以相互替換

因此,可以在模板方法中使用策略模式,就是把那些變化的算法步驟通過使用策略模式來實(shí)現(xiàn),但是具體選取哪個(gè)策略還是要由外部來確定,而整體的算法步驟,也就是算法骨架就由模板方法來定義了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評(píng)論 3 421
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,935評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,892評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,322評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評(píng)論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,800評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,084評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容