为什么一定要学习设计模式

本文节选自《设计模式就该这样学》

先来看一个生活案例,当我们开心时,也许会寻求享乐。在学习设计模式之前,你可能会这样感叹:

学完设计模式之后,你可能会这样感叹:

大家对比一下前后的区别,有何感受?

回到代码中,我们来思考一下,设计模式能解决哪些问题?

1 写出优雅的代码先来看一段我很多年前写的代码。

代码语言:javascript复制

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"));

...

}优化之后的代码如下。

代码语言:javascript复制

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代码为例。

代码语言:javascript复制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里。

代码语言:javascript复制//工具类

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中的方法即可。

代码语言:javascript复制

//增加学生信息

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 list() {

List 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文件,用来存储这些信息。

代码语言:javascript复制

driverClassName=com.mysql.jdbc.Driver

url=jdbc:mysql:///jdbcdemo

username=root

password=root

只需要在工具类JdbcUtil中获取里面的信息即可。

代码语言:javascript复制

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的模板来对代码进行重构。

代码语言:javascript复制

//查询统一模板

public static List query(String sql,Object...params){

List 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 list = JDBCTemplate.query(sql, id);

return list.size()>0? list.get(0):null;

}

public List list() {

String sql = "SELECT * FROM t_student ";

return JDBCTemplate.query(sql);

}

这样重复的代码基本就解决了,但有一个很严重的问题,就是这个程序DQL操作中只能处理Student类和t_student表的相关数据,无法处理其他类,比如Teacher类和t_teacher表。不同的表(不同的对象)应该有不同的列,不同列处理结果集的代码就应该不一样,处理结果集的操作只有DAO自己最清楚。也就是说,处理结果的方法根本就不应该放在模板方法中,应该由每个DAO自己来处理。因此,可以创建一个IRowMapper接口来处理结果集。

代码语言:javascript复制

public interface IRowMapper {

//处理结果集

List rowMapper(ResultSet rs) throws Exception;

}

DQL模板类中调用IRowMapper接口中的handle方法,提醒实现类自己去实现mapping方法。

代码语言:javascript复制

public static List query(String sql,IRowMapper rsh, Object...params){

List 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方法,想要处理什么类型的数据在里面定义即可。

代码语言:javascript复制

public Student get(Long id) {

String sql = "SELECT * FROM t_student WHERE id = ?";

List list = JdbcTemplate.query(sql,new StudentRowMapper(), id);

return list.size()>0? list.get(0):null;

}

public List 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 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类型),还要查询学生数量,这时就要通过泛型来完成。

代码语言:javascript复制

public interface IRowMapper {

//处理结果集

T mapping(ResultSet rs) throws Exception;

}

public static T query(String sql,IRowMapper 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类的代码如下。

代码语言:javascript复制

class StudentRowMapper implements IRowMapper>{

public List mapping(ResultSet rs) throws Exception {

List 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,还可以查询学生数量。

代码语言:javascript复制

public Long getCount(){

String sql = "SELECT COUNT(*) total FROM t_student";

Long totalCount = (Long) JdbcTemplate.query(sql,

new IRowMapper() {

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等经典框架的源码对设计模式展开分析,帮助大家更好、更深入地理解设计模式在框架源码中的落地。

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!