天天看點

Java 設計模式 9 —— 模闆方法模式模闆方法模式

模闆方法模式

定義

在一個方法中定義一個算法的骨架,而将一些步驟延遲到子類中。

模闆方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。

結構

Java 設計模式 9 —— 模闆方法模式模闆方法模式

AbstractClass:包含了模闆方法和兩個模闆方法所用到的抽象函數。

templeteMethod():調用了兩個抽象函數。

void templeteMethod() {
    primitiveOperation1();
    primitiveOperation2();
}
           

ConcreteClass:具體實作了兩個抽象方法,這樣的類可以有很多個。

應用

模闆方法模式是将不可變的代碼或者邏輯類似的代碼抽離出來,放在一個抽象基類中并把可變部分定義為它的抽象方法,封裝進一個算法的步驟,并且允許子類來為可變的部分提供實作。

以使用JDBC查詢資料庫為例,圖中紅框中的代碼為不可變的部分,在所有JDBC查詢中都是一樣的。其餘的代碼則會根據實際的操作而有所變化。

Java 設計模式 9 —— 模闆方法模式模闆方法模式

是以我們使用模闆方法模式來重構它。

public abstract class JDBCTemplete {
    protected String sql = "select * from sys_privilege";
    protected Connection conn = null;
    protected PreparedStatement pstmt = null;
    protected ResultSet rs = null;

    public final Object execute() throws SQLException {
        getConnection();
        getprepareStatement();
        getResultSet();
        Object obj = handle();
        if (isPrint()) {
            printData();
        }
        close();
        return obj;
    }

    public boolean isPrint() {
        return true;
    }

    public void setSql() {}

    public abstract Object handle () throws SQLException;

    public void getConnection() {
        conn = JDBCUtils.getConn();
    }

    public void getprepareStatement() throws SQLException {
        pstmt = conn.prepareStatement(sql);
    }

    public void getResultSet() throws SQLException {
        rs = pstmt.executeQuery();
    }

    public void printData() throws SQLException {
        int columnCount = rs.getMetaData().getColumnCount();

        rs.beforeFirst();
        while (rs.next()) {
            for (int i = 0; i < columnCount; i++) {
                System.out.print(rs.getString(i + 1) + " ");
            }
            System.out.println();
        }
    }

    public void close() throws SQLException {
        if (rs != null) {
            rs.close();
        }

        if (pstmt != null) {
            pstmt.close();
        }

        if (conn != null) {
            conn.close();
        }
    }
}
           

這個模闆方法中包含六個步驟:

  1. findAndPrintAll() 為固定的算法步驟,定義為final是為了防止子類修改步驟的執行順序。
  2. getConnection() 擷取Connection對象。
  3. getprepareStatement() 擷取PrepareStatementd對象。
  4. getResultSet() 擷取ResultSet對象。
  5. handle() 主查詢邏輯。
  6. print() 列印資料。
  7. close() 關閉資源。
  8. isPrint() 決定是否列印資料的鈎子函數。
  9. setSql() 自定義SQL查詢語句的鈎子函數。

現在使用這個模闆來實作一個過程:查詢資料庫中的所有資料封裝在User對象中并傳回一個ArrayList清單,不列印資料。

public class User {
    private Integer id;
    private String name;
    private String url;

    //省略getter和setter
}
           

資料庫中存放的資料:

id privilege_name privilege_url
1 使用者管理 /users
2 角色管理 /roles
3 系統日志 /logs
4 人員維護 /persons
5 機關維護 /companies
public class Templete extends JDBCTemplete {
    @Override
    public boolean isPrint() {
        return false;
    }

    @Override
    public Object handle() throws SQLException {
        ArrayList<User> users = new ArrayList<>();

        while (rs.next()) {
            User user = new User();
            user.setId(Integer.valueOf(rs.getString(1)));
            user.setName(rs.getString(2));
            user.setUrl(rs.getString(3));
        }

        return users;
    }
}
           

測試類:

public class Main {
    public static void main(String[] args) {
        Templete templete = new Templete();
        try {
            ArrayList<User> users = (ArrayList<User>) templete.execute();
            users.forEach(System.out::println);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
           

輸出:

User{id=1, name='使用者管理', url='/users'}
User{id=2, name='角色管理', url='/roles'}
User{id=3, name='系統日志', url='/logs'}
User{id=4, name='人員維護', url='/persons'}
User{id=5, name='機關維護', url='/companies'}
           

使用模闆方法模式之後,主業務中的代碼就會變得非常簡潔。

總結

  1. 保護抽象類中定義算法順序的方法不被子類修改。
  2. 分離可變及不可變部分,讓子類自己決定可變部分的實作。
  3. 讓算法的具體實作對子類開放,對其他類關閉。
  4. 在模闆方法中可以定義一些鈎子函數,來修改模闆方法的執行邏輯而不是執行順序。

繼續閱讀