定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。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()方法。你可随时对其进行重写
识别方法: 模版方法能够经过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为