如今我家裏有一臺鈴木的小車鋒馭和一臺鈴木的摩托車風暴1000,我要想把這兩種類型的車都先跑起來再停下來,有一些步驟,而且這些步驟是有前後順序的,那就是:java
1. 打開車門算法
2. 啓動發動機spring
3. 掛檔sql
4. 走起數據庫
5. 剎車設計模式
6. 停車less
OO設計原則之一就是分離可變和不變的部分並把可變的部分封裝起來,咱們來看一下以上兩種類型的車,哪些步驟的實現是同樣的,哪些是可變的。咱們把不變的部分提取出來並放到超類中讓全部子類共享其行爲,同時咱們把可變部分的具體實現延遲到子類中,讓子類來自行決定如何實現。ide
1. 打開車門(摩托車沒有車門,可變部分)工具
2. 啓動發動機(不變部分)測試
3. 掛檔(汽車用手掛檔,摩托車用腳掛檔,可變部分)
4. 走起(不變部分)
5. 剎車(汽車用腳剎車,摩托車用手剎車,可變部分)
6. 停車(不變部分)
固然以上分離可變及不變部分純屬我的看法,個位看官見仁見智。
若是運用設計模式的方法論,咱們應該採用哪一種模式來很好地知足咱們的需求?
在這種應用場景下我建議使用模板方法模式。
模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟的實現延遲到子類中。模板方法使得子類能夠在不改變算法結構的狀況下,從新定義算法中某些步驟的具體實現。
看到「設計模式」這四個字咱們每每會以爲高深莫測,可是模板方法模式倒是一個例外,你要關注的就是一個方法而已,爲了達到深刻淺出的效果,咱們從一個最簡單的例子開始。
基於以上UML類圖我須要說明幾點模板方法的設計意圖:
1. DriveTemplate是一個抽象類,咱們能夠把一些可變的部分封裝爲抽象方法讓子類去作具體實現。
2. DriveTemplate中的drive方法是final的,這樣是由於咱們不但願子類去覆蓋這個方法,由於這個方法中定義了算法的步驟,咱們不但願子類改變算法的結構。
3. 全部的步驟方法都是protected的訪問修飾符,由於咱們但願具體算法的實現只有子類能夠訪問,對外是不開放的。
咱們再來看看這個簡單例子的代碼實現及測試結果:
模板抽象類
package com.singland.dp.template; public abstract class DriveTemplate { public final void drive() { openDoor(); startEngine(); gear(); go(); brake(); stop(); } protected abstract void openDoor(); protected void startEngine() { System.out.println("engine started !"); } protected abstract void gear(); protected void go() { System.out.println("running..."); } protected abstract void brake(); protected void stop() { System.out.println("stopped !"); } }
小車鋒馭的實現
package com.singland.dp.template; public class SuzukiScross extends DriveTemplate { @Override protected void openDoor() { System.out.println("keyless entry"); } @Override protected void gear() { System.out.println("gear with hand"); } @Override protected void brake() { System.out.println("brake with foot"); } }
摩托車風暴1000的具體實現
package com.singland.dp.template; public class SuzukiStrom1000 extends DriveTemplate { @Override protected void openDoor() { System.out.println("no door actually"); } @Override protected void gear() { System.out.println("gear with foot"); } @Override protected void brake() { System.out.println("brake with hand"); } }
客戶端的測試代碼就很簡單了
package com.singland.dp.template; import org.junit.Test; public class MyTest { @Test public void test() { // DriveTemplate template = new SuzukiStrom1000(); DriveTemplate template = new SuzukiScross(); template.drive(); } }
若是咱們想測試摩托車的實現,只要修改一下測試代碼就行了。
剛纔說到模板方法模式的設計意圖的時候,咱們提到了第2點,咱們不但願子類改變算法的結構或順序,可是在某種場景中,咱們但願子類能有一些自主權,雖然它們不能覆蓋drive方法,可是咱們依然但願子類能夠本身決定一些東西,那麼模板方法模式可否知足這一需求呢?
答案是確定的,咱們來設想這種場景,當咱們在開鋒馭的時候,我但願能夠打開車子的MP3功能來聽歌,可是騎摩托車的時候則不須要。
這樣咱們的UML類圖就須要作一點點小改動:
從類圖能夠看出,咱們在超類中定義了一個music的方法,可是它並非一個抽象方法,這樣子類能夠本身決定是否覆蓋該方法,該方法返回值是一個布爾值的標誌位,默認爲false. 子類SuzukiScross覆蓋了該方法可是SuzukiStorm1000則沒有,咱們再來看看具體的實現:
模板方法類
package com.singland.dp.template; public abstract class DriveTemplate { public final void drive() { openDoor(); startEngine(); gear(); go(); if (music()) { mp3(); } brake(); stop(); } protected abstract void openDoor(); protected void startEngine() { System.out.println("engine started !"); } protected abstract void gear(); protected void go() { System.out.println("running..."); } private void mp3() { System.out.println("music is good"); } protected boolean music() { return false; } protected abstract void brake(); protected void stop() { System.out.println("stopped !"); } }
鋒馭的實現:
package com.singland.dp.template; public class SuzukiScross extends DriveTemplate { @Override protected void openDoor() { System.out.println("keyless entry"); } @Override protected void gear() { System.out.println("gear with hand"); } @Override protected void brake() { System.out.println("brake with foot"); } @Override protected boolean music() { return true; } }
爲節省篇幅,相同的代碼我就不貼出來了。咱們來看看駕駛鋒馭及風暴1000的各自測試結果:
風暴1000
鋒馭
寫到這裏,我來個簡單的總結吧。本質上來講,模板方法設計模式是一個比較容易並且很好理解的模式,在使用這種模式的時候咱們要注意幾點:
1. 保護抽象類中定義算法順序的方法不被子類修改。
2. 分離可變及不可變部分,讓子類本身決定可變部分的實現。
3. 讓算法的具體實現對子類開放,對其餘類關閉。
模板方法模式適用於哪些場景?
讓咱們先來看看一段使用JDBC代碼來操做數據庫中數據的例子:
private void addStudent(Student student) throws Exception { final String SQL = "insert into student (id,studentNumber,firstName,lastName,gender,age,className,major) values (?,?,?,?,?,?,?,?)"; Connection conn = null; PreparedStatement stmt = null; try { conn = dataSource.getConnection(); stmt = conn.prepareStatement(SQL); stmt.setString(1, student.getId()); stmt.setString(2, student.getStudentNumber()); stmt.setString(3, student.getFirstName()); stmt.setString(4, student.getLastName()); stmt.setString(5, student.getGender()); stmt.setInt(6, student.getAge()); stmt.setString(7, student.getClassName()); stmt.setString(8, student.getMajor()); stmt.execute(); } catch(SQLException e) { e.printStackTrace(); } finally { try { if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
以上是一個典型的JDBC實現,咱們先來看看使用JDBC操做數據庫須要通過哪些步驟:
1. 獲取數據庫鏈接
2. 經過數據庫鏈接獲得Statement對象
3. 使用Statement對象進行增刪改查
4. 處理異常
5. 關閉鏈接釋放資源
咱們再來區分一下這些步驟中,哪些是可變部分,哪些是不可變部分:
1. 獲取數據庫鏈接(不可變)
2. 經過數據庫鏈接獲得Statement對象(不可變)
3. 使用Statement對象進行增刪改查(可變)
4. 處理異常(不可變)
5. 關閉鏈接釋放資源(不可變)
咱們能夠看到,在5個步驟中,4個是不可變的,只有一個步驟是可變的,讓我對代碼加一些圖形註釋,這樣就更直觀了:
想一想若是咱們要寫不少這種CRUD的代碼,豈不是要重複寫不少遍這種模板式的代碼?
咱們可使用模板方法模式解決這種問題。
UML類圖我就不畫了,直接上代碼:
模板方法抽象類
package com.studentinfomgt.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; public abstract class JdbcTemplate2 { @Autowired private DataSource dataSource; private Connection connection; protected PreparedStatement statement; protected ResultSet resultSet; public final void dbOperation(String sql, Object entity) throws SQLException { getStatement(sql); crud(entity); releaseResources(); } protected void getStatement(String sql) throws SQLException { connection = dataSource.getConnection(); this.statement = connection.prepareStatement(sql); } protected abstract void crud(Object entity) throws SQLException; private void releaseResources() throws SQLException { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (connection != null) connection.close(); } }
在上面的抽象類中,組織算法順序的方法是dbOperation,算法塊前後是:獲取數據庫鏈接,獲取PreparedStatement, CRUD, 釋放資源
接下來是增長數據到數據庫的具體實現,刪改查我就不貼出來了
package com.studentinfomgt.dao; import java.sql.SQLException; import com.studentinfomgt.pojo.Student; public class JdbcCreateEntity extends JdbcTemplate2 { @Override protected void crud(Object entity) throws SQLException { Student student = (Student) entity; statement.setString(1, student.getId()); statement.setString(2, student.getStudentNumber()); statement.setString(3, student.getFirstName()); statement.setString(4, student.getLastName()); statement.setString(5, student.getGender()); statement.setInt(6, student.getAge()); statement.setString(7, student.getClassName()); statement.setString(8, student.getMajor()); statement.execute(); } }
再來看看個人DAO實現方法是多麼簡潔和簡單:) 由於那些煩人的模板代碼都讓模板去處理了
private void addStudent(Student student) throws Exception { final String SQL = "insert into student (id,studentNumber,firstName,lastName,gender,age,className,major) values (?,?,?,?,?,?,?,?)"; createEntity.dbOperation(SQL, student); }
寫到這裏我再來告訴你,其實咱們不須要重複發明輪子,由於考慮到使用JDBC方式訪問數據庫形成的重複代碼的問題,萬能的Spring早就作好了一個現成的工具JdbcTemplate, 咱們只須要使用這個工具就行了。