菜鸟成长系列-模板方法模式

1,195 阅读6分钟

模板方法模式在sring中有大量的应用,一般我们会使用模板方法来将当前的实现委托给子类来实现,增强代码的可扩展性和复用性。因为涉及到父子类关系,所以模板方法模式是基于“继承”来实现的;模板方法模式属于行为型模式。

简单地说就是,通过父类来定义一系列的算法骨架,并且约定这些方法及其调用顺序,而具体的某些特定方法由子类实现。

先来看一个小demo;我们以写博客来举例子,一般我们写博客的步骤如下:

  • 打开目标网站
  • 打开编辑器
  • 写文章
  • 发布文章

实例代码

首先是定义一个父类,并且提供一个模板方法。

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 抽象模板父类
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public abstract class AbstractTemplateMethod {
    /**
     * 流程方法1:抽象方法,供子类实现
     */
    public abstract void openTargetWebSite();

    /**
     * 流程方法2:子类可选择重写
     */
    public void openMarkDown(){
        System.out.println("打开编辑器");
    }

    /**
     * 流程方法3:抽象方法,供子类实现
     */
    public abstract void writeBlog();

    /**
     * 流程方法4:子类可选择重写
     */
    protected void publisher(){
        System.out.println("发布文章");
    }

    /**
     * 模板方法,此处申明为final,是不希望子类覆盖这个方法,防止更改流程的执行顺序
     */
    public final void templateWriteBlog(){
        openTargetWebSite();
        openMarkDown();
        writeBlog();
        publisher();
    }
}

上面代码中我们提供了一个templateWriteBlog方法,这里方法中包括了写博客的一些流程。在这些流程方法中有些方法父类提供了默认实现,而一些具有差异性的方法则让子类来实现。

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 子类
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class JueJinTemplateMethodPolicy extends AbstractTemplateMethod {
    @Override
    public void openTargetWebSite() {
        System.out.println("打开掘金网站");
    }

    @Override
    public void writeBlog() {
        System.out.println("写一篇Spring相关的文章");
    }
}

子类1:JueJinTemplateMethodPolicy,这个子类中实现了父类中的部分方法,包括:openTargetWebSite和writeBlog。(一般情况下不会去重写父类默认已经实现的方法,仅实现父类中预留的抽象方法来实现)。

package com.glmapper.designmode.mudolmethod;

/**
 * @description: 子类
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class CSDNTemplateMethodPolicy extends AbstractTemplateMethod{

    @Override
    public void openTargetWebSite() {
        System.out.println("打开CSDN网站");
    }

    @Override
    public void writeBlog() {
        System.out.println("写一篇设计模式文章");
    }
}

子类2:CSDNTemplateMethodPolicy,这个子类的作用其实和子类1是一样的,只不过是提供了另外的一种实现策略;(很多情况下,模板方法模式都是和策略模式来联合使用的,通过一套模板机制,对于模板中的部分流程通过不同的策略来实现不同的功能)

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 子类
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class MainTest {
    public static void main(String[] args) {
        AbstractTemplateMethod csdnTemplate = new CSDNTemplateMethodPolicy();
        csdnTemplate.templateWriteBlog();

        AbstractTemplateMethod juejinTemplate = new JueJinTemplateMethodPolicy();
        juejinTemplate.templateWriteBlog();
    }
}

打开CSDN网站
打开编辑器
写一篇设计模式文章
发布文章

打开掘金网站
打开编辑器
写一篇Spring相关的文章
发布文章

上面是客户端代码及输出结果。通过输出我们可以明显的看出,模板中的一些方法将延迟到子类中去实现,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。因此对于模板方法这个模式来说,父类是始终控制着整个流程主动权的,而子类只是辅助父类实现某些可定制的步骤。

模式解析

先看下模板方法模式的类图:

从类图中可以看出,模板方法模式中的角色也是很简单的,主要包括两个角色:

  • 抽象模板(AbstractTemplate):

    • 定义一个或者多个抽象操作,以便于让子类实现。这些抽象操作就是流程中的基本操作(对应的是模板方法中的某个具体的操作方法);这些基本操作是一个顶级逻辑的组成步骤
    • 定义并且实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类中取实现,当然,在这个顶级逻辑中,部分方法也可以由父类来提供默认实现的。
  • 具体类(SubTemplateImpl):

    • 实现父类所定义的一个或者多个抽象方法
    • 每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法的不同实现。

模板方法中的这个方法的概念拆开来说包括两种,一种是模板方法,还有一种是模板方法里面的基本方法。模板方法定义游戏规则,基本方法实现规则中的每个部分。

模板方法带来的优势是显而易见的,它可以帮助我们有效的帮助我们搞定下面的这些场景问题:

  • 封装不变部分,扩展可变部分。
  • 提取公共代码,便于维护。
  • 行为由父类控制,子类实现。

但是缺点也很明显,因为对于每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

典型的模板方法模式的应用

最先想到的就是servlet,servlet的生命周期(以前经常遇到的面试点,现在已经没人问了吧)

  • 初始化 init
  • 处理 service
  • 销毁 destroy

其实这里我觉得也是模板方法的一种体现,虽然在servlet中没有定义顶层的模板方法来控制这个流程(我的想法是这个流程是由容器来控制的,也可能是一种默认的约定)。

在其子类GenericServlet中对init和destroy有了默认的实现,而service方法则是交由子类来实现的,也就是说任何servlet类均必须实现service方法。

这里的service方法就是一个模板方法。service方法中调用了7个do方法中的一个或者几个,完成对客户端的响应,这些do方法需要由HttpServlet的具体子类提供。

HttpServlet中的实现:

protected void service(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince =
                req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg =
            lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

FrameworkServlet中的实现(FrameworkServlet是SpringMVC核心控制器DispatchServlet的父类):

/**
 * Override the parent class implementation in order to intercept
 PATCH requests.
 */
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response)
		throws ServletException, IOException {

	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
		processRequest(request, response);
	}
	else {
		super.service(request, response);
	}
}

关于模板方法模式的学习就到这里了。五一快乐!!!