奶茶在生活中是很常见的,很多商场周边都有看到各种奶茶店。下面通过程序来模拟这一过程:
1.建立奶茶店类:
// 奶茶店
public class MilkTeaShop {
// 制作椰果奶茶
public void makeCoconutMilkTea() {
System.out.println("准备调制杯");
System.out.println("加入牛奶");
System.out.println("加入椰果");
System.out.println("调制奶茶");
System.out.println("包装入奶茶杯");
}
// 制作红茶
public void makeBlackTea() {
System.out.println("准备调制杯");
System.out.println("加入红茶");
System.out.println("加入水");
System.out.println("调制奶茶");
System.out.println("包装入奶茶杯");
}
}
2.客户端使用:
public class Main {
public static void main(String[] args) {
MilkTeaShop milkTeaShop = new MilkTeaShop();
milkTeaShop.makeCoconutMilkTea();
System.out.println("----------------");
milkTeaShop.makeBlackTea();
}
}
3.使用结果:
准备调制杯
加入牛奶
加入椰果
调制奶茶
包装入奶茶杯
----------------
准备调制杯
加入红茶
加入水
调制奶茶
包装入奶茶杯
我们分别定义了制作椰果奶茶和红茶的方法,但是方法内部有着很多重复的代码。其实制作奶茶的步骤可以归结为这几点:准备调制杯-->放入材料-->调制奶茶-->包装入奶茶杯。在这之中最大的不同其实就是放入材料的不同。由此可以通过模板方法模式来对代码进行改经。
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。
角色构成
AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
UML 类图
可以看到模板方法模式结构非常的简单,下面就对代码进行改进:
1.首先是抽象模板类:
// 抽象奶茶类,抽象类角色
public abstract class MilkTea {
// 模板方法
public void makeMilkTea() {
System.out.println("准备调制杯");
putMaterials();
System.out.println("调制奶茶");
System.out.println("包装入奶茶杯");
}
// 抽象方法,需子类实现具体细节
public abstract void putMaterials();
}
2.两个具体奶茶子类:
椰果奶茶类:
// 椰果奶茶类,具体子类角色
public class CoconutMilkTea extends MilkTea {
@Override
public void putMaterials() {
System.out.println("加入牛奶");
System.out.println("加入椰果");
}
}
红茶类
// 红茶类,具体子类角色
public class BlackTea extends MilkTea {
@Override
public void putMaterials() {
System.out.println("加入红茶");
System.out.println("加入水");
}
}
3.奶茶点定义制作奶茶方法
public class MilkTeaShop {
private BlackTea blackTea = new BlackTea();
private CoconutMilkTea coconutMilkTea = new CoconutMilkTea();
public void makeCoconutMilkTea() {
coconutMilkTea.makeMilkTea();
}
public void makeBlackTea() {
blackTea.makeMilkTea();
}
}
4.客户端使用:
public class Main {
public static void main(String[] args) {
MilkTeaShop milkTeaShop = new MilkTeaShop();
milkTeaShop.makeCoconutMilkTea();
System.out.println("----------------");
milkTeaShop.makeBlackTea();
}
}
5.使用结果:
准备调制杯
加入牛奶
加入椰果
调制奶茶
包装入奶茶杯
----------------
准备调制杯
加入红茶
加入水
调制奶茶
包装入奶茶杯
可以看到模板方法模式中最主要的就是模板方法和需要子类重写的抽象方法,也可以在父类中定义好方法,再让子类覆盖以达到不同的功能。
模板方法模式在许多框架中都有极其广泛的应用,还记得最开始写 Java Web 项目是使用的原生Servlet
API ,下面就简单介绍一下在Servlet
中的使用。
1.pom 文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>design-pattern</artifactId>
<groupId>com.phoegel</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<artifactId>template</artifactId>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
</project>
2.编写简单的请求类:
public class HelloWorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("Hello World");
}
}
3.配置 web.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>helloWorldServlet</servlet-name>
<servlet-class>com.phoegel.templatemethod.analysis.HelloWorldServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloWorldServlet</servlet-name>
<url-pattern>/helloWorld</url-pattern>
</servlet-mapping>
</web-app>
4.这里简单配置 tomcat 进行项目启动:
5.启动成功之后,访问地址localhost:8081/helloWorld
,在页面中会打印出
Hello World
通过追踪源码我们发现请求会经过javax.servlet.http.HttpServlet.service(HttpServletRequest req, HttpServletResponse resp)
方法,下面是方法具体内容:
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) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
maybeSetLastModified(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);
}
}
这里的service()
方法就是模板方法,方法内部定义了请求到来时的走向骨架,从中很明显的发现就是根据请求方式的不同调用不同的请求方法,而子类通过重写请求方法来改变整个service()
方法的调用结果。比如说doGet()
方法,在HttpServlet
类中只是简单的输出了错误信息:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
但是我们在HelloWorldServlet
类中重写了doGet()
方法,在页面中就打输出了我们自定义的信息。
模式优点
模式缺点
模式适用场景
本篇文章github代码地址:https://github.com/Phoegel/design-pattern/tree/main/template-method
转载请说明出处,本篇博客地址:https://www.cnblogs.com/phoegel/p/14263839.html
原文:https://www.cnblogs.com/phoegel/p/14263839.html