2. 模板设计模式
2.1 原理与实现
模板方法设计模式( Template Method Design Pattern ),在一个方法中定义一个算法骨架,并将某些步骤推迟
到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
下面是一个简单的Java示例,展示了如何使用模板方法设计模式:
首先,创建一个抽象类,定义算法的骨架:
/**
* 类描述:抽象类定义算法的骨架
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 21:55
*/
@Slf4j
public abstract class AbstractTemplate {
public final void templateMethod() {
step1();
step2();
step3();
}
// 基本方法,定义算法中不会变化的步骤
private void step1() {
log.info("Step 1: Prepare the ingredients.");
}
// 抽象方法,定义算法中需要子类实现的步骤
protected abstract void step2();
// 基本方法,定义算法中不会变化的步骤
protected void step3() {
log.info("Step 3: Serve the dish");
}
}
然后,创建具体的子类,实现抽象类中定义的抽象方法:
// 具体实现A
@Slf4j
public class ConcreteTemplateA extends AbstractTemplate {
@Override
protected void step2() {
log.info("Step 2 (A): Cook the dish using method A.");
}
}
// 具体实现B
@Slf4j
public class ConcreteTemplateB extends AbstractTemplate {
@Override
protected void step2() {
log.info("Step 2 (B): Cook the dish using method B.");
}
}
最后,在客户端代码中使用模板方法:
/**
* 类描述:模板设计模式测试案例
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 22:00
*/
@Slf4j
public class TemplatePatternTest {
@Test
public void test() {
AbstractTemplate templateA = new ConcreteTemplateA();
AbstractTemplate templateB = new ConcreteTemplateB();
log.info("Using Template A:");
templateA.templateMethod();
log.info("Using Template B:");
templateB.templateMethod();
}
}
测试结果:
[main] INFO cn.itcast.designPattern.TemplatePatternTest - Using Template A:
[main] INFO cn.itcast.designPatterns.template.AbstractTemplate - Step 1: Prepare the ingredients.
[main] INFO cn.itcast.designPatterns.template.ConcreteTemplateA - Step 2 (A): Cook the dish using method A.
[main] INFO cn.itcast.designPatterns.template.AbstractTemplate - Step 3: Serve the dish
[main] INFO cn.itcast.designPattern.TemplatePatternTest - Using Template B:
[main] INFO cn.itcast.designPatterns.template.AbstractTemplate - Step 1: Prepare the ingredients.
[main] INFO cn.itcast.designPatterns.template.ConcreteTemplateB - Step 2 (B): Cook the dish using method B.
[main] INFO cn.itcast.designPatterns.template.AbstractTemplate - Step 3: Serve the dish
这个例子中, AbstractTemplate 是一个抽象类,它定义了一个名为templateMethod 的模板方法。该方法包含三个步骤: step1 、step2 和step3 。其中, step1 和step3 是基本方法,它们的实现在抽象类中定义且不会改变。step2 是一个抽象方法,需要子类(如ConcreteTemplateA 和ConcreteTemplateB )根据具体需求实现。客户端代码通过创建子类的实例并调用templateMethod 方法来执行算法。
2.2 源码中的作用
2.2.1 复用
模板模式把一个算法中不变的流程抽象到父类的模板方法 templateMethod() 中,将可变的部分 step2()留给子类来实现。所有的子类都可以复用父类中模板方法定义的流程代码。
(1)Java InputStream
Java IO 类库中,有很多类的设计用到了模板模式,比如 InputStream、OutputStream、Reader、Writer。我们拿 InputStream 来举例说明一下。
在代码中,read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。不过这个方法也被命名为了 read(),只是参数跟模板方法不同。
public abstract class InputStream implements Closeable {
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
// 调用定制方法read(),需要子类给出具体实现
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
// 定制方法read(),需要子类给出具体实现
public abstract int read() throws IOException;
}
这里有一个具体的实现类。用于从一个字节缓冲区中读取一个字节。方法的签名和功能如下:
public class ByteArrayInputStream extends InputStream {
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
}
(2)Java AbstractList
在 Java AbstractList 类中,addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了UnsupportedOperationException 异常, 如果子类不重写是不能使用的。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
// add方法需要子类给出具体实现
add(index++, e);
modified = true;
}
return modified;
}
// 子类重写才能使用
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
}
在子类ArrayList中的是实现
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 重写父类的add方法
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
}
2.2.2 扩展
这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性,基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。我们通过 Junit TestCase、JavaServlet 两个例子来解释一下。
(1)Java Servlet
对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC。利用它,我们只需要关注业务代码的编写,底层的原理几乎不会涉及。但如果我们抛开这些高级框架来开发 Web 项目,必然会用到 Servlet。实际上,使用比较底层的 Servlet 来开发Web 项目也不难。我们只需要定义一个继承 HttpServlet 的类,并且重写其中的doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。具体的代码示例如下所示:
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("Hello World.");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doPost(req, resp);
}
}
除此之外,还需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL 和 Servlet 之间的映射关系。
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.itcast.crysw.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() 方法,然后输出数据(“Helloworld”)到网页。
HttpServlet 的 service() 函数源码:
public abstract class HttpServlet extends GenericServlet {
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 logic
// 子类实现的扩展点
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// 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 less
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 {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
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 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
(2)spring中的核心refresh
spring中存在大量的模板方法,我们列举最核心的refresh方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh =
this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess =
this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
2.3 应用场景
2.3.1 电商系统
将通用逻辑和特定逻辑分离,提高代码复用性和可维护性。以下是一些典型的应用场景:
- 支付流程:电商系统通常需要支持多种支付方式(如信用卡支付、支付宝支付、 微信支付等)。虽然不同的支付方式在实现细节上有所不同,但它们的整体流程是相似的。可以使用模板方法设计模式创建一个支付流程抽象类,定义通用的支付流程骨架,再通过子类实现各种具体支付方式的逻辑。
- 订单处理:电商系统的订单处理流程通常包括一系列步骤,如验证库存、计算价格、生成运单等。这些步骤中有些是通用的,而有些可能因订单类型、商品类 型等因素而异。可以使用模板方法设计模式创建一个订单处理抽象类,定义通用的订单处理流程骨架,然后通过子类实现特定订单类型或商品类型的逻辑。
- 促销策略:电商系统中的促销活动通常具有多种策略(如满减、打折、赠品等)。尽管不同策略的具体实现不同,但它们都需要进行一些通用操作(如获取用户信息、验证促销条件等)。可以使用模板方法设计模式创建一个促销策略抽象类,定义通用的促销操作骨架,然后通过子类实现各种具体促销策略的逻辑。
- 报表生成:电商系统需要生成各种报表(如销售报表、库存报表、财务报表等)。这些报表在数据查询和报表样式上可能有所不同,但它们的生成流程是类似的(如查询数据、生成报表、导出文件等)。可以使用模板方法设计模式创建 一个报表生成抽象类,定义通用的报表生成流程骨架,然后通过子类实现具体报表类型的逻辑。
以支付流程为例:
使用模板方法设计模式创 建一个抽象类 PaymentProcessor 来定义通用的支付流程骨架,然后通过子类实现各种具体支付方式的逻辑。首先,创建一个抽象类 PaymentProcessor 来定义支付流程:
package cn.itcast.designPatterns.template.pay;
import lombok.extern.slf4j.Slf4j;
/**
* 类描述:支付系统抽象类
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 22:36
*/
@Slf4j
public abstract class PaymentProcessor {
// 模板方法,定义支付流程骨架
public final void processPayment(Order order) {
// 获取支付方式(如信用卡、支付宝、微信等)
String paymentMethod = getPaymentMethod();
// 验证订单信息(如订单金额、收货地址等)
validateOrder(order);
// 验证支付信息(如支付账号、支付密码等)
validatePaymentInfo(paymentMethod);
// 执行支付
executePayment(paymentMethod, order);
// 发送支付通知
sendPaymentNotification(order);
}
// 获取支付方式,具体实现由子类提供
protected abstract String getPaymentMethod();
// 验证订单信息,通用逻辑
private void validateOrder(Order order) {
// 验证订单信息的实现
log.info("{}, validateOrder", this.getClass().getSimpleName());
}
// 验证支付信息,具体实现由子类提供
protected abstract void validatePaymentInfo(String paymentMethod);
// 执行支付,具体实现由子类提供
protected abstract void executePayment(String paymentMethod, Order order);
// 发送支付通知,通用逻辑
private void sendPaymentNotification(Order order) {
// 发送支付通知的实现
log.info("{}, sendPaymentNotification", this.getClass().getSimpleName());
}
}
创建一个具体的支付处理器类 AlipayProcessor 来实现支付宝支付方式:
package cn.itcast.designPatterns.template.pay;
import lombok.extern.slf4j.Slf4j;
/**
* 类描述:Alipay实现类
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 22:41
*/
@Slf4j
public class AlipayProcessor extends PaymentProcessor {
@Override
protected String getPaymentMethod() {
return "Alipay";
}
@Override
protected void validatePaymentInfo(String paymentMethod) {
// 验证支付宝支付信息的实现
log.info("{}, validatePaymentInfo", this.getClass().getSimpleName());
}
@Override
protected void executePayment(String paymentMethod, Order order) {
// 执行支付宝支付的实现
log.info("{}, executePayment", this.getClass().getSimpleName());
}
}
同样,我们再创建一个具体的支付处理器类 WechatPayProcessor 来实现微信支付方式:
package cn.itcast.designPatterns.template.pay;
import lombok.extern.slf4j.Slf4j;
/**
* 类描述:WechatPay实现类
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 22:43
*/
@Slf4j
public class WechatPayProcessor extends PaymentProcessor {
@Override
protected String getPaymentMethod() {
return "WechatPay";
}
@Override
protected void validatePaymentInfo(String paymentMethod) {
// 验证微信支付信息的实现
log.info("{}, validatePaymentInfo", this.getClass().getSimpleName());
}
@Override
protected void executePayment(String paymentMethod, Order order) {
// 执行微信支付的实现
log.info("{}, executePayment", this.getClass().getSimpleName());
}
}
提供对外的支付服务:
package cn.itcast.designPatterns.template.pay;
import java.util.Objects;
/**
* 类描述:支付服务业务类
*
* @Author crysw
* @Version 1.0
* @Date 2024/1/21 22:45
*/
public class PaymentService {
public void processPayment(Order order, String paymentType) {
PaymentProcessor paymentProcessor;
if (Objects.equals("Alipay", paymentType)) {
paymentProcessor = new AlipayProcessor();
} else if (Objects.equals("WechatPay", paymentType)) {
paymentProcessor = new WechatPayProcessor();
} else {
throw new IllegalArgumentException("Unsupported payment type: " + paymentType);
}
paymentProcessor.processPayment(order);
}
}
测试案例:
@Slf4j
public class TemplatePatternTest {
@Test
public void testPayment() {
PaymentService paymentService = new PaymentService();
Order order = new Order();
Order.builder().id(1).name("order1").price(5.22).build();
String paymentType = "Alipay";
paymentService.processPayment(order, paymentType);
log.info("===============================");
paymentType = "WechatPay";
paymentService.processPayment(order, paymentType);
}
}
2.4 模板模式 VS 回调
从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式 有较大差别,更像是观察者模式。
从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对 象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
组合优于继承, 回调相对于模板模式会更加灵活。主要体现:
Java是单继承的,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
回调可以不用事先定义类, 使用匿名类来创建回调对象;而模板模式针对不同的实现都要定义不同的子类。
如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,只需要往用到的模板方法中注入回调对象即可。
本文详细介绍了Java中的模板方法设计模式,包括其原理、示例代码和在电商系统中的应用场景。同时,对比了模板模式与回调的异同,强调了回调的灵活性。
3640

被折叠的 条评论
为什么被折叠?



