設計模式學習筆記之九:模板方法模式

如今我家裏有一臺鈴木的小車鋒馭和一臺鈴木的摩托車風暴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, 咱們只須要使用這個工具就行了。

相關文章
相關標籤/搜索