天天看点

设计模式(2):行为型-模板方法模式(Template Method)一.概述二.在Servlet中的应用三.总结

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式分为三种类型,共23种。

创建型模式(5):单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。

结构型模式(7):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

行为型模式(11):(父子类)策略模式、模版方法模式,(两个类)观察者模式、迭代器模式、职责链模式、命令模式,(类的状态)状态模式、备忘录模式,(中间类) 访问者模式、中介者模式、解释器模式。

一.概述

准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现(称为模板方法),然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。

定义

  模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

  Template Method Pattern: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

  模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式。

  模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。

结构

模板方法模式结构比较简单,其核心是抽象类和其中的模板方法的设计,其结构如图所示:

设计模式(2):行为型-模板方法模式(Template Method)一.概述二.在Servlet中的应用三.总结

由图可知,模板方法模式包含如下两个角色:

  • AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
  • ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。

几个概念

  在实现模板方法模式时,开发抽象类的软件设计师和开发具体子类的软件设计师之间可以进行协作。一个设计师负责给出一个算法的轮廓和框架,另一些设计师则负责给出这个算法的各个逻辑步骤。实现这些具体逻辑步骤的方法即为基本方法,而将这些基本方法汇总起来的方法即为模板方法,模板方法模式的名字也因此而来。下面将详细介绍模板方法和基本方法:

  

模板方法: 一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中,并由子类不加以修改地完全继承下来。模板方法是一个具体方法,它给出了一个顶层逻辑框架,而逻辑的组成步骤在抽象类中可以是具体方法,也可以是抽象方法。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。

基本方法: 基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。

  • 抽象方法:抽象方法由抽象类声明,由具体子类实现。在Java语言里抽象方法以abstract关键字标示。
  • 具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
  • 钩子方法:一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现(可使用virtual关键字将其定义为虚函数),并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现。

默认钩子方法:钩子方法常常由抽象类给出一个空实现作为此方法的默认实现。这种空的钩子方法叫做“Do Nothing Hook”。显然,这种默认钩子方法在缺省适配模式里面已经见过了,一个缺省适配模式讲的是一个类为一个接口提供一个默认的空实现,从而使得缺省适配类的子类不必像实现接口那样必须给出所有方法的实现,因为通常一个具体类并不需要所有的方法。

注意:钩子方法的引入不是为了改变父类的逻辑行为。

实现

在模板方法模式中,抽象类的典型代码如下:

abstract class AbstractClass   {    
    //模板方法    
    public void templateMethod(){    
        primitiveOperation1();    
        primitiveOperation2();    
        primitiveOperation3();    
    }    

    //基本方法—具体方法    
    public void primitiveOperation1()  {    
    //实现代码    
    }    

    //基本方法—抽象方法    
    public abstract void primitiveOperation2();        

    //基本方法—钩子方法    
    public void primitiveOperation3()       
    {  }    
}    
           

  在抽象类中,模板方法TemplateMethod()定义了算法的框架,在模板方法中调用基本方法以实现完整的算法,每一个基本方法如PrimitiveOperation1()、PrimitiveOperation2()等均实现了算法的一部分,对于所有子类都相同的基本方法可在父类提供具体实现,例如PrimitiveOperation1(),否则在父类声明为抽象方法或钩子方法,由不同的子类提供不同的实现,例如PrimitiveOperation2()和PrimitiveOperation3()。

  可在抽象类的子类中提供抽象步骤的实现,也可覆盖父类中已经实现的具体方法,具体子类的典型代码如下:

public class ConcreteClass extends AbstractClass  {    
    public void primitiveOperation2(){    
    //实现代码    
    }    

   public void primitiveOperation3()   {    
    //实现代码    
   }    
}   
           

  在模板方法模式中,由于面向对象的多态性,子类对象在运行时将覆盖父类对象,子类中定义的方法也将覆盖父类中定义的方法,因此程序在运行时,具体子类的基本方法将覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对父类行为的反向控制。

二.在Servlet中的应用

使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需继承一个叫HttpServlet的抽象类。HttpServlet类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。下面是service()方法的源代码:

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 == -) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < (lastModified /  * )) {
                    // 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[];
            errArgs[] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
           

当然,这个service()方法也可以被子类置换掉。

可以用TestServlet类来继承HttpServlet类,并且置换掉父类的两个方法:doGet()和doPost()。

public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("using the GET method");

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        System.out.println("using the POST method");
    }

}
           

框架实际上调用的是service()方法,如果是GET请求,service()方法则调用TestServlet 的doGet方法。

三.总结

在模板方法模式中,子类不显式调用父类的方法,而是通过覆盖父类的模板方法来实现某些具体的业务逻辑,父类控制对子类的调用,这种机制被称为好莱坞原则(Hollywood Principle)。

好莱坞原则的定义为:“不要给我们打电话,我们会给你打电话(Don‘t call us, we’ll call you)”。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。

模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,子类不需要调用父类,而通过父类来调用子类,子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用。

钩子方法的作用这里值得探讨下,个人认为钩子方法不应该控制父类的模板方法的逻辑,否则就破坏了这种模式的设计初衷,原则上子类是不应该也不需要知道父类的逻辑。它的作用应该像缺省适配模式下的子类不必像实现接口那样必须给出所有方法的实现。

参考电子书下载:设计模式的艺术–软件开发人员内功修炼之道_刘伟(2013年).pdf

《道德经》第五章:

天地不仁,以万物为刍(chu)狗;圣人不仁,以百姓为刍狗。天地之间,其犹橐龠(tuoyue)乎?虚而不屈(gu),动而俞(俞)出。多闻数穷,不若守于中。

译文:天地是无所谓仁慈的,它没有仁爱,对待万事万物就像对待刍狗一样,任凭万物自生自灭。圣人也是没有仁爱的,也同样像刍狗那样对待百姓,任凭人们自作自息。天地之间,岂不像个风箱一样吗?它空虚而不枯竭,越鼓动风就越多,生生不息。政令繁多反而更加使人困惑,更行不通,不如保持虚静。

继续阅读