模板方法模式

定义

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

模板模式中有两个角色,抽象模板角色定义了一个或多个抽象操作以便让子类实现,定义并实现了一个模板方法,这个方法包含了一些不可改变的方法执行顺序。

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

模板模式中,在抽象模板中定义了方法的执行顺序,而方法的实现可以选择让子类去定制化;另外,统一的不变的方法可以放在抽象模板类中,这样子类可以共用。

模板模式中还经常会出现钩子方法,一般是由抽象类给出方法的空实现,然后子类进行覆写。这种空的钩子方法叫做“Do Nothing Hook”。

钩子方法放到模板方法中,就可以实现生命周期中方法的自定义,例如beforeCreate、beforeUpdate、afterUpdate、afterCreated等。

Java中的模板模式

HttpServlet中应用了模板方法模式,其中HttpServlet是抽象模板角色。

service()是抽象方法,do开头的方法,例如doGet、doPost是可以由子类定制化的方法。

service()方法如下:

    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 / 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()规定好,剩下的doGet等方法可以自定制。

这里的模板方法service没有final修饰,所以这个方法实际上也能进行定制。

模板模式实例

场景:写作文,作文是有固定模板的,例如必须有开头,内容主体和结尾。并且三者编写的顺序不能打乱,不能出现先写结尾再写开头的情况。

三者的顺序就是算法的骨架,而三者的具体实现就会由具体子类去定制化实现,因为每个人写出的作文内容都是不一样的。

根据当前场景设计抽象模板角色:

package com.faith.net.strategy.pay.template;

/**
 * 抽象模板类
 */
public abstract class Article {

    /**
     * 模板方法,定义了算法骨架,执行顺序
     */
    public final void writeArticle() {
        beforeWriteHook();
        writeHead();
        writeBody();
        writeTail();
        afterWriteHook();
    }

    /**
     * 写文章开头
     */
    public abstract void writeHead();

    /**
     * 写文章主体
     */
    public abstract void writeBody();

    /**
     * 写文章结尾,不一定非得是abstract修饰,只要能被子类重写就可以
     */
    protected void writeTail() {
        System.out.println("~~~~~~~~~~~~~~");
    };

    /**
     * 子类公用的方法
     */
    public void writeSeparateLine() {
        System.out.println("~~~~~~~~~~~~~~~~我是分隔线~~~~~~~~~~~~~~~~~~");
    }

    /**
     * 前置钩子方法
     */
    public void beforeWriteHook() {
        System.out.println("酝酿一下感情。。。");
        System.out.println();
    }

    /**
     * 后置钩子方法
     */
    public void afterWriteHook() {
        System.out.println();
        System.out.println("写完了,欣赏一下");
    }
}

添加具体实现,登黄鹤楼和登金陵凤凰台。

package com.faith.net.strategy.pay.template;

/**
 * 登黄鹤楼 崔颢
 */
public class HuangHeLou extends Article {

    @Override
    public void writeHead() {
        System.out.println("昔人已乘黄鹤去,此地空余黄鹤楼。");
    }

    @Override
    public void writeBody() {
        System.out.println("黄鹤一去不复返,白云千载空悠悠。");
        System.out.println("晴川历历汉阳树,芳草萋萋鹦鹉洲。");
    }

    @Override
    public void writeTail() {
        System.out.println("日暮乡关何处是?烟波江上使人愁。");
    }

    @Override
    public void beforeWriteHook() {
        System.out.println("崔颢酝酿一下感情。。。");
        System.out.println();
    }

    @Override
    public void afterWriteHook() {
        System.out.println();
        System.out.println("崔颢写完了,欣赏一下");
    }
}
package com.faith.net.strategy.pay.template;

/**
 * 登金陵凤凰台 李白
 */
public class FengHuangTai extends Article {

    @Override
    public void writeHead() {
        System.out.println("凤凰台上凤凰游,凤去台空江自流。");
    }

    @Override
    public void writeBody() {
        System.out.println("吴宫花草埋幽径,晋代衣冠成古丘。");
        System.out.println("三山半落青天外,二水中分白鹭洲。");
    }

    @Override
    public void writeTail() {
        System.out.println("总为浮云能蔽日,长安不见使人愁。");
    }

    @Override
    public void beforeWriteHook() {
        System.out.println("李白酝酿一下感情。。。");
        System.out.println();
    }

    @Override
    public void afterWriteHook() {
        System.out.println();
        System.out.println("李白写完了,欣赏一下");
    }
}

测试类:

package com.faith.net.strategy.pay.template;

/**
 * 测试类
 */
public class TemplateTest {

    public static void main(String[] args) {
        HuangHeLou huangHeLou = new HuangHeLou();
        huangHeLou.writeArticle();

        System.out.println();System.out.println();System.out.println();

        FengHuangTai fengHuangTai = new FengHuangTai();
        fengHuangTai.writeArticle();
    }
}
上一篇:C++中一个名字查找的小知识


下一篇:shell脚本显示颜色的设置