一段代码的重构过程

原始代码

一个UserDb类中有一个populate方法,这是一个使用JDBC连接数据库,获取相关数据信息的方法,这段代码承担了太多的职责,而且重用性差,一个方法体中代码行数也太多,该如何对它进行重构.

 public void populate() throws Exception {
    Connection c = null;
    try {
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, USER, PASSWORD);
        Statement stmt = c.createStatement();
        ResultSet rs = stmt.executeQuery(SQL);
        while (rs.next()) {
            User user = new User();
            user.setName(rs.getString("name"));
            user.setAge(rs.getInt("age"));
            userList.add(user);
        }
    } finally {
        c.close();
    }
}

重构一—拆分拆分再拆分

可以看出上述代码主要有四个步骤: 1.获得数据库连接 2.通过数据库连接获取结果集 3.遍历结果集,把每一项添加到User列表 4.关闭数据库连接

可以根据这四个步骤,将代码拆分,将其中相关代码抽取成私有方法,最后得到代码如下:

  public void populate() throws Exception {
        Connection c = null;
        try {
            c = getDatabaseConnection();
            ResultSet rs = createResultSet(c);
            while (rs.next())
                addUserToList(rs);               
        } finally {
            c.close();
        }
    }

提取出来的三个私有方法如下:

//获得数据库连接
private Connection getDatabaseConnection() throws SQLException, ClassNotFoundException {
    Connection c = null;
    Class.forName(DRIVER_CLASS);
    c = DriverManager.getConnection(DB_URL, USER, PASSWORD);
    return c;
}
//创建结果集    
private ResultSet createResultSet(Connection c) throws SQLException {
    return c.createStatement().executeQuery(SQL);
}
//将结果集中的数据封装成User对象    
private void addUserToList(ResultSet rs) throws SQLException {
    User user = new User();
    user.setName(rs.getString("name"));
    user.setAge(rs.getInt("age"));
    userList.add(user);
}

重构二 —模板方法模式

上面的方法是否还可以重构,观察可发现,getDatabaseConnection方法和createResultSet方法,前者只是用来取得数据库连接,和结果列表没有什么关系,后者只要传入SQL语句就可以成为通用方法,可以把这两个方法放到一个父类中去,createResultSet方法可以根据模板方法模式,将其设为抽象方法,让子类根据不同的SQL来实现.createResultSet方法被分解成两个方法:一个保留原来的名字,另一个是让子类提供SQL的新的抽象方法(getSql).最终得到AbstractDbBase类如下:

public abstract class AbstractDbBase {
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc://mysql://localhost/";
    private static final String USER = "test";
    private static final String PASSWORD = "123456";

    private Connection getDatabaseConnection() throws SQLException, ClassNotFoundException {
        Connection c = null;
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, USER, PASSWORD);
        return c;
    }

    abstract protected String getSql(); 

     protected ResultSet createResultSet(Connection c) throws SQLException {
        return c.createStatement().executeQuery(getSql());
    }
}

重构三—抽象抽象再抽象

继续观察发现,populate方法只有在while循环里信赖于特定的实体类,从结果集中取出每一项再填充到实体列表,可以抽象出这部分,使用模板方法模式,将populate方法和addEntityToList也移到AbstractDbBase类中.

    abstract protected void addEntityToList(ResultSet rs) throws SQLException;

    public void populate() throws Exception {
        Connection c = null;
        try {
            c = getDatabaseConnection();
            ResultSet rs = createResultSet(c);
            while (rs.next())
                addEntityToList(rs);
        } finally {
            c.close();
        }
    }

重构之后

根据上述几个步骤重构后,UserDb类变成了如下结构:

public class UserDb extends AbstractDbBase{
    private static final String SQL = "select name,age from user";
    private ArrayList userList;

    public UserDb() {
        userList = new ArrayList();
    }

    @Override
    protected void addEntityToList(ResultSet rs) throws SQLException {
        User user = new User();
        user.setName(rs.getString("name"));
        user.setAge(rs.getInt("age"));
        userList.add(user);
    }

    @Override
    protected String getSql() {
     
   return SQL;
    }
}

可见重构之后,UserDb类中最后留下的都是跟User实体相关的方法,变量等,其它可以公用的方法都被抽取到了AbstractDbBase类中,可以被其它实体共用.

总结

  • 1.可复用的代码隐藏在代码的任何地方
  • 2.把代码分解成一步步后,可复用的代码就会暴露出来
  • 3.上述一步步的重构是一种测试驱动开发模式(TDD)
  • 4.尽量做到一个方法代码行数不超过15行
  • 5.每个方法中所有的代码应该处于同一抽象层次
  • 6.抽象,抽象,再抽象,再再抽象……