為什麼一定要學習設計模式

Tom彈架構發表於2021-10-30

先來看一個生活案例,當我們開心時,也許會尋求享樂。在學習設計模式之前,你可能會這樣感嘆:

file

學完設計模式之後,你可能會這樣感嘆:

file

大家對比一下前後的區別,有何感受?

file

回到程式碼中,我們來思考一下,設計模式能解決哪些問題?

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等經典框架的原始碼對設計模式展開分析,幫助大家更好、更深入地理解設計模式在框架原始碼中的落地。

【推薦】Tom彈架構:收藏本文,相當於收藏一本“設計模式”的書

本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術乾貨!

相關文章