摘要:模板方法模式的更多應用事實上很多有關生命周期的類都用到了模板方法模式,最典
把大象裝進冰箱將大象裝進冰箱需要三步,那么老虎了?如何優雅的將大象裝進冰箱?
Step | 大象 | 老虎 | ... |
---|---|---|---|
First | 打開冰箱門 | 打開冰箱門 | 打開冰箱門 |
Second | 把大象放進去 | 把老虎放進去 | ... |
Third | 關閉冰箱門 | 關閉冰箱門 | 關閉冰箱門 |
大象類
public class Elephant {
public void putRefrigerator() {
openDoor();
putElephant();
closeDoor();
}
public void openDoor() {
System.out.println("open the door");
}
public void putElephant() {
System.out.println("put in the Elephant");
}
public void closeDoor() {
System.out.println("close the door");
}
}
老虎類
public class Tiger {
public void putRefrigerator() {
openDoor();
putTiger();
closeDoor();
}
public void openDoor() {
System.out.println("open the door");
}
public void putTiger() {
System.out.println("put in the Tiger");
}
public void closeDoor() {
System.out.println("close the door");
}
}
可以看出我們將大象和老虎放進冰箱的過程中出現了大量的重復代碼,這顯然不是一個好的設計,如果我們在以后的系統升級過程中需要再放入長頸鹿怎么辦,我們應該如何從我們的設計中刪除這些重復代碼?通過觀察我們發現放大象和放老虎之間有很多共同點,都需要進行開關門的操作,只是放的過程不盡相同,我們是否可以將共同點抽離?我們一起試試看
抽象超類
public abstract class AbstractPutAnyAnimal {
//這是一個模板方法,它是一個算法的模板,描述我們將動物放進冰箱的步驟,每一個方法代表了一個步驟
public void putRefrigerator() {
openDoor();
putAnyAnimal();
closeDoor();
}
//在超類中實現共同的方法,由超類來處理
public void openDoor() {
System.out.println("open the door");
}
public void closeDoor() {
System.out.println("close the door");
}
//每個子類可能有不同的方法,我們定義成抽象方法讓子類去實現
abstract void putAnyAnimal();
}
大象類
public class Elephant extends AbstractPutAnyAnimal {
//子類實現自己的業務邏輯
@Override
void putAnyAnimal() {
System.out.println("put in the Elephant");
}
}
老虎類
public class Tiger extends AbstractPutAnyAnimal {
//子類實現自己的業務邏輯
@Override
void putAnyAnimal() {
System.out.println("put in the Tiger");
}
}
通過將相同的方法抽離到超類中,并定義一個抽象方法供子類提供不同的實現,事實上我們剛剛實現了一個模板方法模式。
模板方法模式定義?模板方法模式定義了一個算法的步驟,并允許子類為一個或多個步驟提供實現,putRefrigerator 方法定義了我們將大象裝進冰箱的步驟它就是一個模板方法。模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不在改變算法結構的情況下,重新定義算法的某些步驟(子類提供自己的實現)
模板方法模式中的鉤子我們可以在超類中定義一個空方法,我們稱這種方法為鉤子(hook)。子類可以依據情況選擇覆蓋,鉤子的存在可以讓子類有能力對算法的不同點進行掛載;鉤子可以讓子類實現算法中的可選部分,鉤子也可以讓子類為抽象類做一些決定我們將大象裝進冰箱后可能會想調整冰箱溫度,也可能什么都不做使用默認溫度,我們可以通過定義一個鉤子,讓子類來選擇是否調整溫度,如下:
抽象父類
public abstract class AbstractPutAnyAnimal {
public void putRefrigerator() {
openDoor();
putAnyAnimal();
closeDoor();
//默認為false,重新這個方法決定是否執行addTemperature();方法
if (isAdd()) {
addTemperature();
}
}
public void openDoor() {
System.out.println("open the door");
}
public void closeDoor() {
System.out.println("close the door");
}
abstract void putAnyAnimal();
void addTemperature(){
System.out.println("plus one");
};
//定義一個空實現,由子類決定是否對其進行實現
boolean isAdd(){
return false;
}
}
大象類
public class Elephant extends AbstractPutAnyAnimal {
@Override
void putAnyAnimal() {
System.out.println("put in the Elephant");
}
//子類實現鉤子方法
@Override
boolean isAdd() {
return true;
}
}
我們通過定義一個鉤子方法,子類選擇是否實現這個鉤子方法,來決定是否調整溫度;當然鉤子方法的用途不止如此,它還能讓子類有機會對模板中即將發生或剛剛發生的步驟做出反應,這在JDK中有很多的例子,甚至在前端開發領域也有很多例子,我就不具體展開代碼演示了,后面在模板方法模式的更多應用中展開。
傳統JDBC編程JDK以及Spring中使用了很多的設計模式,下面我們通過比較傳統JDBC編程和JDBCTemplate來看看模板方法模式是如何幫我們消除樣板代碼的
JDBC編程之新增
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test);;
String username = "root";
String password = "1234";
Connection connection = null;
Statement statement = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
String sql = "insert into users(nickname,comment,age) values(小小譚,I love three thousand times, 21)";
statement = connection.createStatement();
int i = statement.executeUpdate(sql);
return i;
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (null != statement) {
statement.close();
}
if (null != connection) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return 0;
JDBC編程之查詢
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test);;
String username = "root";
String password = "1234";
Connection connection = null;
Statement statement = null;
try{
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
String sql = "select nickname,comment,age from users";
statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
List usersList = new ArrayList<>();
while (resultSet.next()) {
Users users = new Users();
users.setNickname(resultSet.getString(1));
users.setComment(resultSet.getString(2));
users.setAge(resultSet.getInt(3));
usersList.add(users);
}
return usersList;
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (null != statement) {
statement.close();
}
if (null != connection) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return null;
上面給出了我們在傳統JDBC編程中的兩個案例,可以看到傳統JDBC的很多缺點,當然在實際項目中我們可能不會這么原始的進行數據庫開發,可能會對JDBC進行一定的封裝,方便我們的使用。Spring 官方為了簡化JDBC的開發也發布了JDBCTemplate,下面我們就看一下它是如何簡化開發的,以及模板方法模式在其中的應用
JDBCTemplate是個啥,它到底簡化了什么?從JDBCTemplate的名字我們就不難看出,它簡化了我們JDBC的開發,而且很可能大量應用了模板方法模式,它到底為我們提供了什么?它提供了與平臺無光的異常處理機制。使用過原生JDBC開發的同學可能有經歷,幾乎所有的操作代碼都需要我們強制捕獲異常,但是在出現異常時我們往往無法通過異常讀懂錯誤。Spring解決了我們的問題它提供了多個數據訪問異常,并且分別描述了他們拋出時對應的問題,同時對異常進行了包裝不強制要求我們進行捕獲,同時它為我們提供了數據訪問的模板化,從上面的傳統JDBC編程我們可以發現,很多操作其實是重復的不變得比如事務控制、資源的獲取關閉以及異常處理等,同時結果集的處理實體的綁定,參數的綁定這些東西都是特有的。因此Spring將數據訪問過程中固定部分和可變部分劃分為了兩個不同的類(Template)和回調(Callback),模板處理過程中不變得部分,回調處理自定義的訪問代碼;下面我們具體通過源碼來學學習一下
模板方法模式在JDBCTemplate中的應用我所使用的版本是5.1.5.RELEASE
打開JdbcTemplate類(我這里就不截圖了,截圖可能不清晰我直接將代碼copy出來):
JdbcTemplate
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
//查詢前綴
private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";
//計數前綴
private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";
//是否跳過警告
private boolean ignoreWarnings = true;
//查詢大小
private int fetchSize = -1;
//最大行
private int maxRows = -1;
//查詢超時
private int queryTimeout = -1;
//是否跳過結果集處理
private boolean skipResultsProcessing = false;
//是否跳過非公共結果集處理
private boolean skipUndeclaredResults = false;
//map結果集是否大小寫敏感
private boolean resultsMapCaseInsensitive = false;
public JdbcTemplate() {
}
//調用父類方法設置數據源和其他參數
public JdbcTemplate(DataSource dataSource) {
this.setDataSource(dataSource);
this.afterPropertiesSet();
}
//調用父類方法設置數據源,懶加載策略和其他參數
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
this.setDataSource(dataSource);
this.setLazyInit(lazyInit);
this.afterPropertiesSet();
}
}
JdbcTemplate 繼承了JdbcAccessor實現了JdbcOperations,JdbcAccessor主要封裝了數據源的操作,JdbcOperations主要定義了一些操作接口。我們一起看一下JdbcOperations類;
public abstract class JdbcAccessor implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
//數據源
@Nullable
private DataSource dataSource;
//異常翻譯
@Nullable
private volatile SQLExceptionTranslator exceptionTranslator;
//懶加載策略
private boolean lazyInit = true;
public JdbcAccessor() {
}
public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource;
}
@Nullable
public DataSource getDataSource() {
return this.dataSource;
}
protected DataSource obtainDataSource() {
DataSource dataSource = this.getDataSource();
Assert.state(dataSource != null, "No DataSource set");
return dataSource;
}
public void setDatabaseProductName(String dbName) {
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
}
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
}
之所以前面提到spring讓我們更方便的處理異常就是這里他包裝了一個SQLExceptionTranslator,其他的代碼都是做數據源的檢查之類的設置數據源,我們看一下其中getExceptionTranslator()方法
public SQLExceptionTranslator getExceptionTranslator() {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
if (exceptionTranslator != null) {
return exceptionTranslator;
} else {
synchronized(this) {
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
if (exceptionTranslator == null) {
DataSource dataSource = this.getDataSource();
if (dataSource != null) {
exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
} else {
exceptionTranslator = new SQLStateSQLExceptionTranslator();
}
this.exceptionTranslator = (SQLExceptionTranslator)exceptionTranslator;
}
return (SQLExceptionTranslator)exceptionTranslator;
}
}
}
這是一個標準的單例模式,我們在學習模板方法模式的路途中有捕獲了一個野生的單例;我們繼續看JdbcOperations接口我們調其中一個接口進行解析;
@Nullable
T execute(StatementCallback var1) throws DataAccessException;
StatementCallback 接口
@FunctionalInterface
public interface StatementCallback<T> {
@Nullable
T doInStatement(Statement var1) throws SQLException, DataAccessException;
}
execute實現
@Nullable
public T execute(StatementCallback action) throws DataAccessException {
//參數檢查
Assert.notNull(action, "Callback object must not be null");
//獲取連接
Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
Statement stmt = null;
Object var11;
try {
//創建一個Statement
stmt = con.createStatement();
//設置查詢超時時間,最大行等參數(就是一開始那些成員變量)
this.applyStatementSettings(stmt);
//執行回調方法獲取結果集
T result = action.doInStatement(stmt);
//處理警告
this.handleWarnings(stmt);
var11 = result;
} catch (SQLException var9) {
//出現錯誤優雅退出
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, this.getDataSource());
con = null;
throw this.translateException("StatementCallback", sql, var9);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, this.getDataSource());
}
return var11;
}
這一個方法可謂是展現的淋漓盡致,這是一個典型的模板方法+回調模式,我們不需要再寫過多的重復代碼只需要實現自己獲取result的方法就好(StatementCallback)事實上我們自己也不需要實現這個方法,繼續向上看,我們是如何調用execute方法的,以查詢為例,我們看他是如何一步步調用的:
查詢方法
public List findAll() {
JdbcTemplate jdbcTemplate = DataSourceConfig.getTemplate();
String sql = "select nickname,comment,age from users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper(Users.class));
}
query實現
public List query(String sql, RowMapper rowMapper) throws DataAccessException {
return (List)result(this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));
}
這里的RowMapper是負責將結果集中一行的數據映射成實體返回,用到了反射技術,這里就不展開了,有興趣的同學可以自己打開源碼閱讀,繼續向下:
query實現
@Nullable
public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Executing SQL query [" + sql + "]");
}
//實現回調接口
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
QueryStatementCallback() {
}
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
Object var3;
try {
//這里真正的執行我們的sql語句
rs = stmt.executeQuery(sql);
//處理對象映射
var3 = rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
}
return var3;
}
public String getSql() {
return sql;
}
}
//調用execute接口
return this.execute((StatementCallback)(new QueryStatementCallback()));
}
看到這里相信你也不得拍手稱奇,Spring處理的非常巧妙,請繼續向下看:
update詳解
protected int update(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss) throws DataAccessException {
this.logger.debug("Executing prepared SQL update");
return updateCount((Integer)this.execute(psc, (ps) -> {
Integer var4;
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (this.logger.isTraceEnabled()) {
this.logger.trace("SQL update affected " + rows + " rows");
}
var4 = rows;
} finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer)pss).cleanupParameters();
}
}
return var4;
}));
}
為什么我要把update函數拎出來講了,因為update這里使用了lambda函數,回想我們StatementCallback定義只有一個方法的接口,他就是一個函數是接口,所以他是一個函數式接口,所以這里直接使用lambda語法,lambda函數允許你直接內連,為函數接口的抽象方法提供實現,并且整個表達式作為函數接口的一個實例。我們在平時學習中可能知道了lambda語法但是可能使用的較少,或者不知道如何用于實戰,那么多閱讀源碼一定可以提升你的實戰能力。 我們可以看到JDBCTemplate使用了很多回調。為什么要用回調(Callback));如果父類有多個抽象方法,子類需要全部實現這樣特別麻煩,而有時候某個子類只需要定制父類中的某一個方法該怎么辦呢?這個時候就要用到Callback回調了就可以完美解決這個問題,可以發現JDBCTemplate并沒有完全拘泥于模板方法,非常靈活。我們在實際開發中也可以借鑒這種方法。
模板方法模式的更多應用事實上很多有關生命周期的類都用到了模板方法模式,最典型的也是可能我們最熟悉的莫過于Servlet了,廢話不多說上源碼
public abstract class HttpServlet extends GenericServlet
{
}
HttpServlet的所有方法,我們看到HttpServlet繼承了GenericServlet,我們繼續看:
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
private transient ServletConfig config;
public GenericServlet() { }
//沒有實現鉤子
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameter(name);
}
public Enumeration getInitParameterNames() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletName();
}
}
可以看到這就是個典型的模板方法類蠻,而且鉤子函數也在這里展現的淋漓盡致,如init、destroy方法等,JDK中很多類都是用了模板方法等著你發現哦。
模板方法模式在Vue.js中的應用模板方法模式在其他語言中也有實現比如Vue.js、React中;比如Vue生命周期肯定使用了模板方法,我就不對源碼展開分析了。
總結
設計模式在Spring中得到了大量的應用,感興趣的同學可以看看Spring源碼加以學習,如果你覺得我寫的還不錯的話點個贊吧,如果你發現了錯誤,或者不好的地方也可以及時告訴我加以改正,謝謝!您的贊賞和批評是進步路上的好伙伴。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7162.html
摘要:模板方法模式的更多應用事實上很多有關生命周期的類都用到了模板方法模式,最典 將大象裝進冰箱需要三步,那么老虎了?如何優雅的將大象裝進冰箱? 把大象裝進冰箱 Step 大象 老虎 ... First 打開冰箱門 打開冰箱門 打開冰箱門 Second 把大象放進去 把老虎放進去 ... Third 關閉冰箱門 關閉冰箱門 關閉冰箱門 大象類 public ...
摘要:模板方法模式的更多應用事實上很多有關生命周期的類都用到了模板方法模式,最典 將大象裝進冰箱需要三步,那么老虎了?如何優雅的將大象裝進冰箱? 把大象裝進冰箱 Step 大象 老虎 ... First 打開冰箱門 打開冰箱門 打開冰箱門 Second 把大象放進去 把老虎放進去 ... Third 關閉冰箱門 關閉冰箱門 關閉冰箱門 大象類 public ...
摘要:目錄建造者模式應用。其實不用也可以,因為不是很復雜,只是為了復習一下所學過的設計模式知識目錄工廠模式應用。 為了提高開發效率,通常會想辦法把一些模式固定的重復性的勞動抽取出來,以后再使用的時候,拿來主義就可以了。這樣既可以提高開發效率,又降低了出錯的風險。 這一思想在我們的日常工作中可以說隨處可見,我們完成一項復雜的工程,并不需要面面俱到什么都自己寫,我們完全可以利用第三方的jar包讓...
摘要:簡單工廠模式的實質是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類。中的就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得對象,但是否是在傳入參數后創建還是傳入參數前創建這個要根據具體情況來定。中的就是典型的工廠方法模式。 showImg(https://segmentfault.com/img/bVbwbd9?w=640&h=492); 一. 簡單工廠又叫做靜態工廠方法(...
摘要:簡單工廠模式的實質是由一個工廠類根據傳入的參數,動態決定應該創建哪一個產品類。中的就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得對象,但是否是在傳入參數后創建還是傳入參數前創建這個要根據具體情況來定。 設計模式作為工作學習中的枕邊書,卻時常處于勤說不用的尷尬境地,也不是我們時常忘記,只是一直沒有記憶。 Spring作為業界的經典框架,無論是在架構設計方面,還是在代碼編寫方面,都堪...
閱讀 713·2023-04-25 19:43
閱讀 3910·2021-11-30 14:52
閱讀 3784·2021-11-30 14:52
閱讀 3852·2021-11-29 11:00
閱讀 3783·2021-11-29 11:00
閱讀 3869·2021-11-29 11:00
閱讀 3557·2021-11-29 11:00
閱讀 6105·2021-11-29 11:00