先來看一個生活案例,當我們開心時,也許會尋求享樂。在學習設計模式之前,你可能會這樣感嘆:
學完設計模式之後,你可能會這樣感嘆:
大家對比一下前後的區別,有何感受?
回到程式碼中,我們來思考一下,設計模式能解決哪些問題?
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彈架構 』可獲取更多技術乾貨!