模闆方法模式
定義
在一個方法中定義一個算法的骨架,而将一些步驟延遲到子類中。
模闆方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
結構
AbstractClass:包含了模闆方法和兩個模闆方法所用到的抽象函數。
templeteMethod():調用了兩個抽象函數。
void templeteMethod() {
primitiveOperation1();
primitiveOperation2();
}
ConcreteClass:具體實作了兩個抽象方法,這樣的類可以有很多個。
應用
模闆方法模式是将不可變的代碼或者邏輯類似的代碼抽離出來,放在一個抽象基類中并把可變部分定義為它的抽象方法,封裝進一個算法的步驟,并且允許子類來為可變的部分提供實作。
以使用JDBC查詢資料庫為例,圖中紅框中的代碼為不可變的部分,在所有JDBC查詢中都是一樣的。其餘的代碼則會根據實際的操作而有所變化。
是以我們使用模闆方法模式來重構它。
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();
}
}
}
這個模闆方法中包含六個步驟:
- findAndPrintAll() 為固定的算法步驟,定義為final是為了防止子類修改步驟的執行順序。
- getConnection() 擷取Connection對象。
- getprepareStatement() 擷取PrepareStatementd對象。
- getResultSet() 擷取ResultSet對象。
- handle() 主查詢邏輯。
- print() 列印資料。
- close() 關閉資源。
- isPrint() 決定是否列印資料的鈎子函數。
- 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'}
使用模闆方法模式之後,主業務中的代碼就會變得非常簡潔。
總結
- 保護抽象類中定義算法順序的方法不被子類修改。
- 分離可變及不可變部分,讓子類自己決定可變部分的實作。
- 讓算法的具體實作對子類開放,對其他類關閉。
- 在模闆方法中可以定義一些鈎子函數,來修改模闆方法的執行邏輯而不是執行順序。