定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟java
假設正在開發一款分析公司文檔的數據挖掘程序。用戶須要向程序輸入各類格式(PDF、DOC或CSV)的文檔,程序則會從這些文檔中抽取出有意義的數據,並以統一的格式將其返回給用戶。一段時間後,你發現這三個類包含了許多類似的代碼。儘管這些類處理不一樣數據格式的代碼徹底不一樣,可是數據處理和分析的代碼卻徹底同樣。怎樣能在保持算法結構完整的狀況下去除重複代碼呢?另外,還有一個與使用這些類的客戶端代碼相關的問題:客戶端代碼中包含許多條件語句,以根據不一樣的處理對象類型選擇合適的處理過程。若是全部處理數據的類都擁有相同的接口或基類, 那麼你就能夠去除客戶端代碼中的條件語句, 轉而使用多態機制來在處理對象上調用函數算法
模板方法模式建議將算法分解爲一系列步驟,而後將這些步驟改寫爲方法,最後在 「模板方法」 中依次調用這些方法。步驟能夠是抽象的,也能夠有一些默認的實現。爲了可以使用算法,客戶端須要自行提供子類並實現全部的抽象步驟。若有必要還需重寫一些步驟(但這一步中不包括模板方法自身)。有兩種類型的步驟:網絡
還有另外一種名爲鉤子的步驟。鉤子是內容爲空的可選步驟。即便不重寫鉤子,模板方法也能工做。鉤子一般放置在算法重要步驟的先後,爲子類提供額外的算法擴展點 框架
模板方法是一種代碼複用的基本技術。它們在類庫中尤其重要,提取了類庫中的公共行爲。模板方法致使了一種反向的控制結構,這種結構有時被稱爲好萊塢法則,即一個父類調用一個子類的操做,而不是相反函數
模板方法調用下列類型的操做:post
1. 具體操做(ConcreteClass或對客戶類的操做)this
2. 具體的AbstractClass的操做spa
3. 原語操做(AbstractClass中定義抽象的原語操做,具體的子類將重定義它們以實現一個算法的各步驟).net
4. 工廠方法3d
5. 鉤子操做,它提供了缺省行爲,子類能夠在必要時進行擴展。鉤子操做在一般狀況下是空操做
本例中,模版方法模式定義了一個可與社交網絡協做的算法。與特定社交網絡相匹配的子類將根據社交網絡所提供的API來實現這些步驟
networks/Network.java: 基礎社交網絡類
package template_method.networks; /** * @author GaoMing * @date 2021/7/25 - 21:47 * Base class of social network. */ public abstract class Network { String userName; String password; Network() {} /** * Publish the data to whatever network. */ public boolean post(String message) { // Authenticate before posting. Every network uses a different // authentication method. if (logIn(this.userName, this.password)) { // Send the post data. boolean result = sendData(message.getBytes()); logOut(); return result; } return false; } abstract boolean logIn(String userName, String password); abstract boolean sendData(byte[] data); abstract void logOut(); }
networks/Facebook.java: 具體社交網絡
package template_method.networks; /** * @author GaoMing * @date 2021/7/25 - 21:47 */ public class Facebook extends Network { public Facebook(String userName, String password) { this.userName = userName; this.password = password; } public boolean logIn(String userName, String password) { System.out.println("\nChecking user's parameters"); System.out.println("Name: " + this.userName); System.out.print("Password: "); for (int i = 0; i < this.password.length(); i++) { System.out.print("*"); } simulateNetworkLatency(); System.out.println("\n\nLogIn success on Facebook"); return true; } public boolean sendData(byte[] data) { boolean messagePosted = true; if (messagePosted) { System.out.println("Message: '" + new String(data) + "' was posted on Facebook"); return true; } else { return false; } } public void logOut() { System.out.println("User: '" + userName + "' was logged out from Facebook"); } private void simulateNetworkLatency() { try { int i = 0; System.out.println(); while (i < 10) { System.out.print("."); Thread.sleep(500); i++; } } catch (InterruptedException ex) { ex.printStackTrace(); } } }
networks/Twitter.java: 另外一個社交網絡
package template_method.networks; /** * @author GaoMing * @date 2021/7/25 - 21:47 */ public class Twitter extends Network{ public Twitter(String userName, String password) { this.userName = userName; this.password = password; } public boolean logIn(String userName, String password) { System.out.println("\nChecking user's parameters"); System.out.println("Name: " + this.userName); System.out.print("Password: "); for (int i = 0; i < this.password.length(); i++) { System.out.print("*"); } simulateNetworkLatency(); System.out.println("\n\nLogIn success on Twitter"); return true; } public boolean sendData(byte[] data) { boolean messagePosted = true; if (messagePosted) { System.out.println("Message: '" + new String(data) + "' was posted on Twitter"); return true; } else { return false; } } public void logOut() { System.out.println("User: '" + userName + "' was logged out from Twitter"); } private void simulateNetworkLatency() { try { int i = 0; System.out.println(); while (i < 10) { System.out.print("."); Thread.sleep(500); i++; } } catch (InterruptedException ex) { ex.printStackTrace(); } } }
Demo.java: 客戶端代碼
package template_method; import template_method.networks.Network; import template_method.networks.Twitter; import template_method.networks.Facebook; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author GaoMing * @date 2021/7/25 - 21:47 */ public class Demo { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); Network network = null; System.out.print("Input user name: "); String userName = reader.readLine(); System.out.print("Input password: "); String password = reader.readLine(); // Enter the message. System.out.print("Input message: "); String message = reader.readLine(); System.out.println("\nChoose social network for posting message.\n" + "1 - Facebook\n" + "2 - Twitter"); int choice = Integer.parseInt(reader.readLine()); // Create proper network object and send the message. if (choice == 1) { network = new Facebook(userName, password); } else if (choice == 2) { network = new Twitter(userName, password); } network.post(message); } }
運行結果
Input user name: Jhonatan Input password: qswe Input message: Hello, World! Choose social network for posting message. 1 - Facebook 2 - Twitter 2 Checking user's parameters Name: Jhonatan Password: **** .......... LogIn success on Twitter Message: 'Hello, World!' was posted on Twitter User: 'Jhonatan' was logged out from Twitter
使用示例:模版方法模式在Java框架中很常見。開發者一般使用它來向框架用戶提供經過繼承實現的、對標準功能進行擴展的簡單方式
這裏是一些核心 Java 程序庫中模版方法的示例:
java.io.InputStream、java.io.OutputStream、java.io.Reader和java.io.Writer的全部非抽象方法
java.util.AbstractList、java.util.AbstractSet和java.util.AbstractMap的全部非抽象方法
javax.servlet.http.HttpServlet,全部默認發送HTTP 405 「方法不容許」 錯誤響應的doXXX()方法。你可隨時對其進行重寫
識別方法: 模版方法能夠經過行爲方法來識別, 該方法已有一個在基類中定義的 「默認」 行爲