首页 > 设计模式 > 设计模式之模板方法

设计模式之模板方法

这一节记录一下模板方法,在学模板方法之前,要先学习一下什么是模板,模板其实就是一个例子,例如我们做市场调研时,调研人员会给我们一个表格,我们只需要回答一些答案即可,映射到我们的代码中就是这个样子:

aaaaaaaa
bbbbbbbb
cccccccc
********
dddddddd
eeeeeeee
########
ffffffff
gggggggg
$$$$$$$$

这是我们第一次写的代码,但是我们在后来的coding中发现,这一段代码我们需要copy一份,只需要该其中的第四行、第七行、第十行就行了,其余的不需要做修改,如果我们copy过去,改一下这么做虽然可以完成,但我们想一想如果我们代码里面一万处这样的代码,难道要copy一万次?这么做是不是很冗余?这时候就需要我们的模板方法上场了,下面看一个我们在实际中的典型应用:JDBC操作数据库,所以我们下面先看一个操作数据库的实际例子:

package cn.bridgeli.demo.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.bridgeli.demo.entity.User;
import cn.bridgeli.demo.util.DBUtil;

public class UserDao {

    public User getUserById(int userId) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        User user = null;
        String sql = "SELECT * FROM user WHERE id = ?";

        try {
            conn = DBUtil.getConn();
            pstmt = DBUtil.getPstmt(conn, sql);
            pstmt.setInt(1, userId);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                user = new User();
                //
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(rs);
            DBUtil.close(pstmt);
            DBUtil.close(conn);
        }
        return user;
    }
}

这段代码经过我们分析,很明显就建行不一样:String SQL不一样,pStmt设置参数不一样,if(rs.next())不一样,所以我们可以把它做成模板方法,下面我们就重构着一段代码。在重构这段代码之前有几个问题我们先要明确:
1. 我们返回的不一定是一个user,而且不只是一个,为了解决这个问题,返回改成List(Object);
2. pStmt设置参数不一样,我们可以把这一段改成一个类的一个方法;
3. 同样if(rs.next())这个也可以把这段改成一个类的一个方法。
所以就变成了这样:

package cn.bridgeli.demo.template;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import cn.bridgeli.demo.util.DBUtil;

public class JDBCTemplate {

    public List<Object> query(String sql, JDBCCallBack jdbcCallBack) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        List<Object> data = null;
        try {
            conn = DBUtil.getConn();
            pstmt = DBUtil.getPstmt(conn, sql);

            jdbcCallBack.setParam(pstmt);

            rs = pstmt.executeQuery();
            if (rs.next()) {
                data = new ArrayList<Object>();

                Object object = jdbcCallBack.rsToObject(rs);

                data.add(object);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(rs);
            DBUtil.close(pstmt);
            DBUtil.close(conn);
        }
        return data;
    }

    public Object queryOne(String sql, JDBCCallBack jdbcCallBack) {
        List<Object> data = query(sql, jdbcCallBack);
        if (null != data && !data.isEmpty()) {
            return data.get(0);
        }
        return null;
    }
}

这样的话,我们的UserDao类就变成了这样:

package cn.bridgeli.demo.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.bridgeli.demo.entity.User;
import cn.bridgeli.demo.template.JDBCCallBack;
import cn.bridgeli.demo.template.JDBCTemplate;
import cn.bridgeli.demo.util.DBUtil;

public class UserDao {

    public User getUserById(int userId) {
        String sql = "SELECT * FROM user";
        return (User) JDBCTemplate.queryOne(sql, new JDBCCallBack() {

            @Override
            public void setParam(PreparedStatement pstmt) {
                // TODO Auto-generated method stub

            }

            @Override
            public Object rsToObject(ResultSet rs) {
                // TODO Auto-generated method stub
                return null;
            }
        });
    }
}

这样的话,我们在查询的时候,是不是变得特别简单了,我们只需要重写我们JDBCCallBack中的两个方法就好了,这样的话我们是不是最大限度的实现了代码复用。但是这么一来也有一个问题:我们是不是永远都是需要重写这两个方法呢?举个最简单的例子:如果我们使用:当我们插入数据的时候,是不是就没有rsToObject()方法了?为了解决这个问题我们可以写一个抽象类去实现JDBCCallBack接口,这样的话我们只需要new这个抽象类,重写需要我们重写的方法就可以了,这样的话是不是就解决了这个问题了,其实解决这个问题的方法也是一个设计模式:适配器模式!将来有机会我们在具体讲,这里大家知道就好了。

另外再多说一点,在写模板方法时,其实很多时候我们第一次写的时候,我们并不知道这个方法是可以做成模板方法的,所以你就大胆的写吧,但我们想拷一个方法时,这个时候你就要小心了,是不是可以做成模板方法了,当第三次的时候,肯定是要去重构代码了,当然这么写就完美了吗?既然这么问,肯定还不完美,问题就是:在我们UserDao里面getUserById()我们直接写死了User对象,这样是不是就导致我们我们的这个方法就不能够复用了?有人说这个不能复用很正常啊,因为你是getUserById(),其实我们在深入的想一想,我们要实现的是不是getById(),因为我们如果有一万个需要获取的对象,是不要这个方法又要写一万次?这样的话,代码是不是又要写一万次?代码有重复了,所以我们很自然而然的就想到了可以写成:泛型!下面我们就用泛型试着重构一下我们的代码,因为泛型不支持静态方法,所以肯定就不静态方法了,这样的感兴趣的可以自己试一试(其实说简单不简单,说难也不难)

最后再说两点,1. 其实众所周知,我们的servlet也是一个模板方法,而且还没有template这个类,那么他是怎么实现的呢?servlet是方法级的模板方法,而我们这个实现可以说是对象级别的模板方法,感兴趣的可以自己看一下servlet的源码,实现一点也不难,我相信大家是能够看懂的;2. 我们的JDBCTemplate相信大家看出来了,因为他是无状态的类,是可以做成单例的,这个也交给大家去实现了,因为前面已有介绍单例的文章,当然提到单例,大家肯定可以想的到:工厂模式!

分享到:
作 者: BridgeLi,http://www.bridgeli.cn/
原文链接:https://www.bridgeli.cn/archives/121
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.