模板设计模式

本文详细介绍了Java中的模板方法设计模式,包括其原理、示例代码和在电商系统中的应用场景。同时,对比了模板模式与回调的异同,强调了回调的灵活性。

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

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是单继承的,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。

回调可以不用事先定义类, 使用匿名类来创建回调对象;而模板模式针对不同的实现都要定义不同的子类。

如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,只需要往用到的模板方法中注入回调对象即可。

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值