先来看一个生活案例,当我们开心时,也许会寻求享乐。在学习设计模式之前,你可能会这样感叹:
学完设计模式之后,你可能会这样感叹:
大家对比一下前后的区别,有何感受?
回到代码中,我们来思考一下,设计模式能解决哪些问题?
1 写出优雅的代码
先来看一段我很多年前写的代码。
public void setExammingForm(ExammingForm curForm,String parameters)throws BaseException { | |
... | |
JSONObject jsonObj = new JSONObject(parameters); | |
//试卷主键 | |
if(jsonObj.getString("examinationPaper_id")!= null && (!jsonObj.getString ("examinationPaper_id").equals(""))) | |
curForm.setExaminationPaper_id(jsonObj.getLong("examinationPaper_id")); | |
//剩余时间 | |
if(jsonObj.getString("leavTime") != null && (!jsonObj.getString("leavTime").equals(""))) | |
curForm.setLeavTime(jsonObj.getInt("leavTime")); | |
//单位主键 | |
if(jsonObj.getString("organization_id")!= null && (!jsonObj.getString ("organization_id").equals(""))) | |
curForm.setOrganization_id(jsonObj.getLong("organization_id")); | |
//考试主键 | |
if(jsonObj.getString("id")!= null && (!jsonObj.getString("id").equals(""))) | |
curForm.setId(jsonObj.getLong("id")); | |
//考场主键 | |
if(jsonObj.getString("examroom_id")!= null && (!jsonObj.getString ("examroom_id").equals(""))) | |
curForm.setExamroom_id(jsonObj.getLong("examroom_id")); | |
//用户主键 | |
if(jsonObj.getString("user_id")!= null && (!jsonObj.getString("user_id").equals(""))) | |
curForm.setUser_id(jsonObj.getLong("user_id")); | |
//专业代码 | |
if(jsonObj.getString("specialtyCode")!= null && (!jsonObj.getString ("specialtyCode").equals(""))) | |
curForm.setSpecialtyCode(jsonObj.getLong("specialtyCode")); | |
//报考岗位 | |
if(jsonObj.getString("postionCode")!= null && (!jsonObj.getString ("postionCode").equals(""))) | |
curForm.setPostionCode(jsonObj.getLong("postionCode")); | |
//报考等级 | |
if(jsonObj.getString("gradeCode")!= null && (!jsonObj.getString ("gradeCode").equals(""))) | |
curForm.setGradeCode(jsonObj.getLong("gradeCode")); | |
//考试开始时间 | |
curForm.setExamStartTime(jsonObj.getString("examStartTime")); | |
//考试结束时间 | |
curForm.setExamEndTime(jsonObj.getString("examEndTime")); | |
... | |
} |
优化之后的代码如下。
public class ExammingFormVo extends ExammingForm{ | |
private String examinationPaperId; //试卷主键 | |
private String leavTime; //剩余时间 | |
private String organizationId; //单位主键 | |
private String id; //考试主键 | |
private String examRoomId; //考场主键 | |
private String userId; //用户主键 | |
private String specialtyCode; //专业代码 | |
private String postionCode; //报考岗位 | |
private String gradeCode; //报考等级 | |
private String examStartTime; //考试开始时间 | |
private String examEndTime; //考试结束时间 | |
... | |
} | |
public void setExammingForm(ExammingForm form,String parameters)throws BaseException { | |
try { | |
JSONObject json = new JSONObject(parameters); | |
ExammingFormVo vo = JSONObject.parseObject(json,ExammingFormVo.class); | |
form = vo; | |
}catch (Exception e){ | |
e.printStackTrace(); | |
} | |
} |
2 更好地重构项目
平时我们写的代码虽然满足了需求,但往往不利于项目的开发与维护,以下面的JDBC代码为例。
public void save(Student stu){ | |
String sql = "INSERT INTO t_student(name,age) VALUES(?,?)"; | |
Connection conn = null; | |
Statement st = null; | |
try{ | |
//1. 加载注册驱动 | |
Class.forName("com.mysql.jdbc.Driver"); | |
//2. 获取数据库连接 | |
conn=DriverManager.getConnection("jdbc:mysql:///jdbc_demo","root","root"); | |
//3. 创建语句对象 | |
PreparedStatement ps=conn.prepareStatement(sql); | |
ps.setObject(1,stu.getName()); | |
ps.setObject(2,stu.getAge()); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
}catch(Exception e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(st != null) | |
st.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(conn != null) | |
conn.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
//删除学生信息 | |
public void delete(Long id){ | |
String sql = "DELETE FROM t_student WHERE id=?"; | |
Connection conn = null; | |
Statement st = null; | |
try{ | |
//1. 加载注册驱动 | |
Class.forName("com.mysql.jdbc.Driver"); | |
//2. 获取数据库连接 | |
conn=DriverManager.getConnection("jdbc:mysql:///jdbc_demo","root","root"); | |
//3. 创建语句对象 | |
PreparedStatement ps = conn.prepareStatement(sql); | |
ps.setObject(1,id); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
}catch(Exception e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(st != null) | |
st.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(conn != null) | |
conn.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
//修改学生信息 | |
public void update(Student stu){ | |
String sql = "UPDATE t_student SET name=?,age=? WHERE id=?"; | |
Connection conn = null; | |
Statement st = null; | |
try{ | |
//1. 加载注册驱动 | |
Class.forName("com.mysql.jdbc.Driver"); | |
//2. 获取数据库连接 | |
conn=DriverManager.getConnection("jdbc:mysql:///jdbc_demo","root","root"); | |
//3. 创建语句对象 | |
PreparedStatement ps = conn.prepareStatement(sql); | |
ps.setObject(1,stu.getName()); | |
ps.setObject(2,stu.getAge()); | |
ps.setObject(3,stu.getId()); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
}catch(Exception e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(st != null) | |
st.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
}finally{ | |
try{ | |
if(conn != null) | |
conn.close(); | |
}catch(SQLException e){ | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
上述代码的功能没问题,但是代码重复得太多,因此可以进行抽取,把重复代码放到一个工具类JdbcUtil里。
//工具类 | |
public class JdbcUtil { | |
private JdbcUtil() { } | |
static { | |
//1. 加载注册驱动 | |
try { | |
Class.forName("com.mysql.jdbc.Driver"); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public static Connection getConnection() { | |
try { | |
//2. 获取数据库连接 | |
return DriverManager.getConnection("jdbc:mysql:///jdbc_demo", "root", "root"); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return null; | |
} | |
//释放资源 | |
public static void close(ResultSet rs, Statement st, Connection conn) { | |
try { | |
if (rs != null) | |
rs.close(); | |
} catch (SQLException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (st != null) | |
st.close(); | |
} catch (SQLException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (conn != null) | |
conn.close(); | |
} catch (SQLException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
只需要在实现类中直接调用工具类JdbcUtil中的方法即可。
//增加学生信息 | |
public void save(Student stu) { | |
String sql = "INSERT INTO t_student(name,age) VALUES(?,?)"; | |
Connection conn = null; | |
PreparedStatement ps=null; | |
try { | |
conn = JDBCUtil.getConnection(); | |
//3. 创建语句对象 | |
ps = conn.prepareStatement(sql); | |
ps.setObject(1, stu.getName()); | |
ps.setObject(2, stu.getAge()); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(null, ps, conn); | |
} | |
} | |
//删除学生信息 | |
public void delete(Long id) { | |
String sql = "DELETE FROM t_student WHERE id=?"; | |
Connection conn = null; | |
PreparedStatement ps = null; | |
try { | |
conn=JDBCUtil.getConnection(); | |
//3. 创建语句对象 | |
ps = conn.prepareStatement(sql); | |
ps.setObject(1, id); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(null, ps, conn); | |
} | |
} | |
//修改学生信息 | |
public void update(Student stu) { | |
String sql = "UPDATE t_student SET name=?,age=? WHERE id=?"; | |
Connection conn = null; | |
PreparedStatement ps = null; | |
try { | |
conn=JDBCUtil.getConnection(); | |
//3. 创建语句对象 | |
ps = conn.prepareStatement(sql); | |
ps.setObject(1, stu.getName()); | |
ps.setObject(2, stu.getAge()); | |
ps.setObject(3, stu.getId()); | |
//4. 执行SQL语句 | |
ps.executeUpdate(); | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(null, ps, conn); | |
} | |
} | |
public Student get(Long id) { | |
String sql = "SELECT * FROM t_student WHERE id=?"; | |
Connection conn = null; | |
Statement st = null; | |
ResultSet rs = null; | |
PreparedStatement ps=null; | |
try { | |
conn = JDBCUtil.getConnection(); | |
//3. 创建语句对象 | |
ps = conn.prepareStatement(sql); | |
ps.setObject(1, id); | |
//4. 执行SQL语句 | |
rs = ps.executeQuery(); | |
if (rs.next()) { | |
String name = rs.getString("name"); | |
int age = rs.getInt("age"); | |
Student stu = new Student(id, name, age); | |
return stu; | |
} | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(rs, ps, conn); | |
} | |
return null; | |
} | |
public List<Student> list() { | |
List<Student> list = new ArrayList<>(); | |
String sql = "SELECT * FROM t_student "; | |
Connection conn = null; | |
Statement st = null; | |
ResultSet rs = null; | |
PreparedStatement ps=null; | |
try { | |
conn=JDBCUtil.getConnection(); | |
//3. 创建语句对象 | |
ps = conn.prepareStatement(sql); | |
//4. 执行SQL语句 | |
rs = ps.executeQuery(); | |
while (rs.next()) { | |
long id = rs.getLong("id"); | |
String name = rs.getString("name"); | |
int age = rs.getInt("age"); | |
Student stu = new Student(id, name, age); | |
list.add(stu); | |
} | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(rs, ps, conn); | |
} | |
return list; | |
} | |
虽然完成了重复代码的抽取,但数据库中的账号密码等直接显示在代码中,不利于后期账户密码改动的维护。可以建立一个db.propertise文件,用来存储这些信息。
driverClassName=com.mysql.jdbc.Driver | |
url=jdbc:mysql:///jdbcdemo | |
username=root | |
password=root | |
只需要在工具类JdbcUtil中获取里面的信息即可。
static { | |
//1. 加载注册驱动 | |
try { | |
ClassLoader loader = Thread.currentThread().getContextClassLoader(); | |
InputStream inputStream = loader.getResourceAsStream("db.properties"); | |
p = new Properties(); | |
p.load(inputStream); | |
Class.forName(p.getProperty("driverClassName")); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public static Connection getConnection() { | |
try { | |
//2. 获取数据库连接 | |
return DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), | |
p.getProperty("password")); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return null; | |
} | |
代码抽取到这里,貌似已经完成,但在实现类中,依然存在部分重复代码,在DML操作中,除了SQL和设置值的不同,其他都相同,把相同的部分抽取出来,把不同的部分通过参数传递进来,无法直接放在工具类中。此时,可以创建一个模板类JdbcTemplate,创建一个DML和DQL的模板来对代码进行重构。
//查询统一模板 | |
public static List<Student> query(String sql,Object...params){ | |
List<Student> list=new ArrayList<>(); | |
Connection conn = null; | |
PreparedStatement ps=null; | |
ResultSet rs = null; | |
try { | |
conn=JDBCUtil.getConnection(); | |
ps=conn.prepareStatement(sql); | |
//设置值 | |
for (int i = 0; i < params.length; i++) { | |
ps.setObject(i+1, params[i]); | |
} | |
rs = ps.executeQuery(); | |
while (rs.next()) { | |
long id = rs.getLong("id"); | |
String name = rs.getString("name"); | |
int age = rs.getInt("age"); | |
Student stu = new Student(id, name, age); | |
list.add(stu); | |
} | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(rs, ps, conn); | |
} | |
return list; | |
} | |
实现类直接调用方法即可。 | |
//增加学生信息 | |
public void save(Student stu) { | |
String sql = "INSERT INTO t_student(name,age) VALUES(?,?)"; | |
Object[] params=new Object[]{stu.getName(),stu.getAge()}; | |
JdbcTemplate.update(sql, params); | |
} | |
//删除学生信息 | |
public void delete(Long id) { | |
String sql = "DELETE FROM t_student WHERE id = ?"; | |
JdbcTemplate.update(sql, id); | |
} | |
//修改学生信息 | |
public void update(Student stu) { | |
String sql = "UPDATE t_student SET name = ?,age = ? WHERE id = ?"; | |
Object[] params=new Object[]{stu.getName(),stu.getAge(),stu.getId()}; | |
JdbcTemplate.update(sql, params); | |
} | |
public Student get(Long id) { | |
String sql = "SELECT * FROM t_student WHERE id=?"; | |
List<Student> list = JDBCTemplate.query(sql, id); | |
return list.size()>0? list.get(0):null; | |
} | |
public List<Student> list() { | |
String sql = "SELECT * FROM t_student "; | |
return JDBCTemplate.query(sql); | |
} | |
这样重复的代码基本就解决了,但有一个很严重的问题,就是这个程序DQL操作中只能处理Student类和t_student表的相关数据,无法处理其他类,比如Teacher类和t_teacher表。不同的表(不同的对象)应该有不同的列,不同列处理结果集的代码就应该不一样,处理结果集的操作只有DAO自己最清楚。也就是说,处理结果的方法根本就不应该放在模板方法中,应该由每个DAO自己来处理。因此,可以创建一个IRowMapper接口来处理结果集。
public interface IRowMapper { | |
//处理结果集 | |
List rowMapper(ResultSet rs) throws Exception; | |
} | |
DQL模板类中调用IRowMapper接口中的handle方法,提醒实现类自己去实现mapping方法。
public static List<Student> query(String sql,IRowMapper rsh, Object...params){ | |
List<Student> list = new ArrayList<>(); | |
Connection conn = null; | |
PreparedStatement ps=null; | |
ResultSet rs = null; | |
try { | |
conn = JdbcUtil.getConnection(); | |
ps = conn.prepareStatement(sql); | |
//设置值 | |
for (int i = 0; i < params.length; i++) { | |
ps.setObject(i+1, params[i]); | |
} | |
rs = ps.executeQuery(); | |
return rsh.mapping(rs); | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JdbcUtil.close(rs, ps, conn); | |
} | |
return list ; | |
} | |
实现类自己去实现IRowMapper接口的mapping方法,想要处理什么类型的数据在里面定义即可。
public Student get(Long id) { | |
String sql = "SELECT * FROM t_student WHERE id = ?"; | |
List<Student> list = JdbcTemplate.query(sql,new StudentRowMapper(), id); | |
return list.size()>0? list.get(0):null; | |
} | |
public List<Student> list() { | |
String sql = "SELECT * FROM t_student "; | |
return JdbcTemplate.query(sql,new StudentRowMapper()); | |
} | |
class StudentRowMapper implements IRowMapper{ | |
public List mapping(ResultSet rs) throws Exception { | |
List<Student> list=new ArrayList<>(); | |
while(rs.next()){ | |
long id = rs.getLong("id"); | |
String name = rs.getString("name"); | |
int age = rs.getInt("age"); | |
Student stu=new Student(id, name, age); | |
list.add(stu); | |
} | |
return list; | |
} | |
} | |
到这里为止,实现ORM的关键代码已经大功告成,但是DQL查询不单单要查询学生信息(List类型),还要查询学生数量,这时就要通过泛型来完成。
public interface IRowMapper<T> { | |
//处理结果集 | |
T mapping(ResultSet rs) throws Exception; | |
} | |
public static <T> T query(String sql,IRowMapper<T> rsh, Object...params){ | |
Connection conn = null; | |
PreparedStatement ps=null; | |
ResultSet rs = null; | |
try { | |
conn = JdbcUtil.getConnection(); | |
ps = conn.prepareStatement(sql); | |
//设置值 | |
for (int i = 0; i < params.length; i++) { | |
ps.setObject(i+1, params[i]); | |
} | |
rs = ps.executeQuery(); | |
return rsh.mapping(rs); | |
//5. 释放资源 | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JdbcUtil.close(rs, ps, conn); | |
} | |
return null; | |
} | |
StudentRowMapper类的代码如下。
class StudentRowMapper implements IRowMapper<List<Student>>{ | |
public List<Student> mapping(ResultSet rs) throws Exception { | |
List<Student> list=new ArrayList<>(); | |
while(rs.next()){ | |
long id = rs.getLong("id"); | |
String name = rs.getString("name"); | |
int age = rs.getInt("age"); | |
Student stu=new Student(id, name, age); | |
list.add(stu); | |
} | |
return list; | |
} | |
} | |
这样,不仅可以查询List,还可以查询学生数量。
public Long getCount(){ | |
String sql = "SELECT COUNT(*) total FROM t_student"; | |
Long totalCount = (Long) JdbcTemplate.query(sql, | |
new IRowMapper<Long>() { | |
public Long mapping(ResultSet rs) throws Exception { | |
Long totalCount = null; | |
if(rs.next()){ | |
totalCount = rs.getLong("total"); | |
} | |
return totalCount; | |
} | |
}); | |
return totalCount; | |
} | |
这样,重构设计就已经完成,好的代码能让我们以后维护更方便,因此学会对代码的重构是非常重要的。
3 经典框架都在用设计模式解决问题
比如,Spring就是一个把设计模式用得淋漓尽致的经典框架。本书会结合JDK、Spring、MyBatis、Netty、Tomcat、Dubbo等经典框架的源码对设计模式展开分析,帮助大家更好、更深入地理解设计模式在框架源码中的落地。