概述
模板設計模式(Template Design Pattern)是一種行為型設計模式,它定義了一個算法的骨架,將算法的一些步驟延遲到子類中實現。模板設計模式允許子類在不改變算法結構的情況下重新定義算法的某些步驟。
模板設計模式的核心思想是:將一個算法的主要結構定義在一個模板方法中,而將具體(某些)步驟的實現交給子類去完成。
// 模板類 抽象類:Beverage-->飲料
abstract class Beverage {// 模板方法,定義算法的骨架public final void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}// 具體步驟,由子類實現abstract void brew();abstract void addCondiments();// 公共方法void boilWater() {System.out.println("Boiling water");}void pourInCup() {System.out.println("Pouring into cup");}
}// 具體子類
class Coffee extends Beverage {void brew() {System.out.println("Dripping coffee through filter");}void addCondiments() {System.out.println("Adding sugar and milk");}
}class Tea extends Beverage {void brew() {System.out.println("Steeping the tea");}void addCondiments() {System.out.println("Adding lemon");}
}// 客戶端代碼
public class TemplateExample {public static void main(String[] args) {Beverage coffee = new Coffee();Beverage tea = new Tea();System.out.println("Making coffee:");coffee.prepareRecipe();System.out.println("\nMaking tea:");tea.prepareRecipe();}
}
在這個示例中,Beverage 是模板類,定義了模板方法 prepareRecipe(),其中包含了煮水、沖泡、倒入杯子和加調料的步驟。brew() 和 addCondiments() 是具體步驟(沖泡和加調料),由子類實現。Coffee 和 Tea 是具體子類,分別實現了不同的沖泡和調料步驟。
通過模板設計模式,模板類 Beverage 提供了一個通用的算法骨架,而具體步驟的實現交給子類。這樣可以確保算法的結構一致,同時允許不同子類根據自身特點進行實現。
使用場景、源碼應用
模板設計模式在許多場景下都可以應用,特別是在需要定義一組具有共同流程的操作時,但每個操作可能有不同的實現細節。以下是一些常見的應用場景:
-
框架和庫:許多框架和庫使用模板設計模式來定義通用的操作流程,然后允許用戶通過子類來實現特定的操作細節。比如,數據庫操作框架可以定義一個通用的操作流程,然后用戶可以通過繼承來實現特定數據庫的連接和操作。
-
算法實現:在某些算法中,有一些步驟是通用的,但有些步驟可能因情況而異。模板設計模式允許你將通用的步驟放在模板方法中,然后由子類來實現不同的步驟。
-
工作流程:在工作流程管理中,可以使用模板設計模式來定義通用的工作流程,然后讓不同的流程實例來實現具體的任務。
-
生命周期管理:在許多應用中,有一些生命周期的操作是通用的,例如初始化、清理資源等。模板設計模式可以用于定義這些通用的生命周期操作。
在源碼中,模板設計模式也有許多應用。以下是一些示例:
-
Java Servlet:在 Java Servlet 中,HttpServlet 就是一個使用模板設計模式的例子。HttpServlet 定義了 service() 方法作為模板方法,然后具體的 HTTP 請求處理由不同的子類來實現。
-
JUnit 測試框架:在 JUnit 中,測試用例的執行過程也是一個典型的模板設計模式。JUnit 提供了測試用例的生命周期方法,例如 setUp() 和 tearDown(),然后用戶可以在子類中實現這些方法來執行測試。
-
Spring Framework:在 Spring 中,JdbcTemplate 類用于執行數據庫操作,它將數據庫操作的通用流程定義在模板方法中,而具體的 SQL 執行由用戶提供的回調函數實現。
這些只是一些示例,模板設計模式在許多框架和庫中都有廣泛的應用,它提供了一種結構化的方式來定義通用的操作流程,并允許具體實現在子類中進行定制。
Java Servlet
對于 Java Web 項目開發來說,常用的開發框架是 SpringMVC。利用它,我們只需要關注業務代碼的編寫,底層的原理幾乎不會涉及。但是,如果我們拋開這些高級框架來開發 Web 項目,必然會用到 Servlet。實際上,使用比較底層的 Servlet 來開發 Web 項目也不難。我們只需要定義一個繼承 HttpServlet 的類,并且重寫其中的 doGet() 或 doPost() 方法,來分別處理 get 和 post 請求。具體的代碼示例如下所示:
public class HelloServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("Hello World.");}
}
除此之外,我們還需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等 Servlet 容器在啟動的時候,會自動加載這個配置文件中的 URL 和 Servlet 之間的映射關系。
<servlet><servlet-name>HelloServlet</servlet-name><servlet-class>com.xzg.cd.HelloServlet</servlet-class>
</servlet>
<servlet-mapping><servlet-name>HelloServlet</servlet-name><url-pattern>/hello</url-pattern>
</servlet-mapping>
當我們在瀏覽器中輸入網址(比如,http://127.0.0.1:8080/hello )的時候,Servlet 容器會接收到相應的請求,并且根據 URL 和 Servlet 之間的映射關系,找到相應的 Servlet(HelloServlet),然后執行它的 service() 方法。service() 方法定義在父類 HttpServlet 中,它會調用 doGet() 或 doPost() 方法,然后輸出數據(“Hello world”)到網頁。我們現在來看,HttpServlet 的 service() 函數長什么樣子。
public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException{HttpServletRequest request;HttpServletResponse response;if (!(req instanceof HttpServletRequest &&res instanceof HttpServletResponse)) {throw new ServletException("non-HTTP request or response");}request = (HttpServletRequest) req;response = (HttpServletResponse) res;service(request, response);
}protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince < lastModified) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);// 子類實現的擴展點doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {// 子類實現的擴展點doPost(req, resp);} else if (method.equals(METHOD_PUT)) {// 子類實現的擴展點doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {// 子類實現的擴展點doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {// 子類實現的擴展點doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {// 子類實現的擴展點doTrace(req,resp);} else {String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}
從上面的代碼中我們可以看出,HttpServlet 的 service() 方法就是一個模板方法,它實現了整個 HTTP 請求的執行流程,**doGet()、doPost() 是模板中可以由子類來定制的部分。**實際上,這就相當于 Servlet 框架提供了一個擴展點(doGet()、doPost() 方法),讓框架用戶在不用修改 Servlet 框架源碼的情況下,將業務代碼通過擴展點鑲嵌到框架中執行。
模板模式與Callback回調函數有何區別和聯系?
聯系:
- 共同點:兩者都涉及將一些邏輯從調用代碼中抽離出來,使代碼更具模塊化和可維護性。
- 抽象步驟:在模板模式中,一個通用的算法框架定義了一系列的抽象步驟,子類可以通過實現這些步驟來完成特定的行為。在回調函數中,一個函數可以接受一個回調函數作為參數,使調用者能夠在適當的時候執行這個回調函數,完成特定的操作。
區別:
-
角色和目的:
- 模板模式:主要目的是在超類中定義算法的骨架,而將一些具體步驟的實現推遲到子類中。它更關注整個流程的結構和控制。
- 回調函數:主要目的是允許調用者在某個代碼塊執行時插入自己的代碼邏輯。它更關注于將執行權交給外部代碼,以便根據需要執行回調邏輯。
-
控制權:
- 模板模式:控制權由超類控制,子類只實現具體的步驟,流程由模板方法決定。
- 回調函數:控制權在調用者手中,調用者通過提供回調函數來決定在何時執行回調邏輯。
-
調用關系:
- 模板模式:子類通過繼承超類來實現抽象步驟,超類負責調用子類的方法。
- 回調函數:調用者將回調函數作為參數傳遞給被調用者,被調用者在適當的時候調用回調函數。
舉例:
一個具體的區別和聯系示例可以是在GUI編程中,比如在按鈕被點擊時要執行的操作。使用模板模式,你可以定義一個通用的按鈕點擊流程,包括按鈕的渲染、點擊事件的處理等。使用回調函數,你可以將點擊事件處理的邏輯作為一個回調函數傳遞給按鈕組件,以便在按鈕被點擊時執行。
Java中的 java.util.concurrent 包中的一些類使用了回調來實現多線程編程。例如,Executor 接口中的 execute(Runnable command) 方法就接受一個 Runnable 對象作為回調,用于在線程池中執行任務。
總之,模板模式和回調函數在不同的場景下有不同的應用,但都關注于提高代碼的模塊化和可重用性,同時也都涉及到將一些代碼邏輯從調用者中分離出來。