学习设计模式——模板模式

550 阅读3分钟

今天,我们来学习行为型设计模式中的第二个:模板模式

模板模式主要是用来解决复用扩展 两个问题。

话不多说,开始今天的学习。

介绍

模板模式(Template Method Design Pattern): 模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

这里的算法骨架就是“模板”,通用的代码实现很简单。

原理与实现

AbstracTemplate.java

public abstract class AbstracTemplate {

    public final void templateMethod() {
        methodA();
        methodB();
    }

    protected abstract void methodA();
    protected abstract void methodB();

}

ConcreteClassA.java、ConcreteClassB.java

public class ConcreteClassA extends AbstracTemplate {
    @Override
    protected void methodA() {
        System.out.println("执行了 ConcreteClassA 的方法 methodA");
    }

    @Override
    protected void methodB() {
        System.out.println("执行了 ConcreateClassA 的方法 methodB");
    }
}


public class ConcreteClassB extends AbstracTemplate {

    @Override
    protected void methodA() {
        System.out.println("执行了 ConcreteClassB 的方法 methodA");
    }

    @Override
    protected void methodB() {
        System.out.println("执行了 ConcreteClassB 的方法 methodB");
    }
}

Test.java

public class Test {

    public static void main(String[] args) {

        AbstracTemplate classA = new ConcreteClassA();
        classA.templateMethod();
        System.out.println("===============================");
        AbstracTemplate classB = new ConcreteClassB();
        classB.templateMethod();

    }

}

结果:

image.png

作用一:复用

模板模式的作用之一:复用

模板模式把一个算法中不变的流程抽象到父类的模板方法 templateMethod()中,将可变的部分 methodA()、methodB() 留给子类 ConcreteClassA 和 ConcreteClassB 来实现。所有的子类都可以复用父类中模板方法定义的流程代码。

Java AbstractList

在 Java AbstractList 类中,addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportedOperationException 异常。前提是,如果子类不重写是不能使用的。

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    boolean modified = false;
    for (E e : c) {
        add(index++, e);
        modified = true;
    }
    return modified;
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

还有在Java IO 类库汇总,有很多的类的设计也用到了模板模式,比如 InputStream、OutputStream、Reader、Writer。

作用二:扩展

模板模式的作用之二:扩展。

基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。 还记得当初学 Java Web 开发时,必然会学到 Servlet。使用 Servlet 来开发 Web 项目时,我们需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。具体代码示例如下所示:

public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello World!");
    }
}

当请求到这个 Servlet 的时候会执行 它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用 doGet() 或 doPost() 方法,然后输出数据 “Hello world” 到网页。

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;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                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);
    }

}

可以看到,service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost() 是模板中可以由子类来定制的部分。实际上,这就相当于 Servlet 框架提供了一个扩展点(doGet()、doPost() 方法),让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。

总结

模板模式的优点:

  • 具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
  • 代码复用的基本技术,在数据库设计中尤为重要。
  • 存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。