Java设计模式-模板方式模式

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

(图片来源: 设计模式:可复用面向对象软件的基础)

Tips

处理某个流程的骨架代码已经具备, 但其中某节点的具体实现暂不确定, 此时可采用模板方法, 将该节点的代码实现转移给子类完成. 即: 处理步骤在父类中定义好, 具体实现延迟到子类中定义.

模式实现

到ATM取款机办理业务, 都会经过插卡、输密码、处理业务、取卡 等几个过程, 而且这几个过程一定是顺序执行的, 且除了 处理业务 (如取款、改密、查账) 可能会有所不同之外, 其他的过程完全相同. 因此我们就可以参考模板方法模式把插卡、输密码、取卡 3个过程放到父类中实现, 并定义一个流程骨架, 然后将 处理业务的具体逻辑 放到子类中:

AbstractClass 抽象模板:

定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤.

实现一个模板方法,定义一个算法的骨架. 该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作.

/**
 * @author jifang
 * @since 16/8/21 上午10:35.
 */
public abstract class AbstractATMBusiness {
    public void run() {
        System.out.println("-> 插卡");
        System.out.println("-> 输入并校验密码");
        if (checkPassword()) {
            onBusiness();
        }
        System.out.println("-> 取卡");
    }
    // 具体业务处理延迟到子类实现
    protected abstract void onBusiness();
    private boolean checkPassword() {
        // TODO Encode Password, Select DB & Comparison
        return true;
    }
}

AbstractATMBusiness是一个模板方法, 它定义了ATM操作的一个主要步骤并确定他们的先后顺序, 但允许子类改变这些具体步骤以满足各自的需求.

ConcreteClass

实现原语操作以完成算法中与特定子类相关的步骤; 每个AbstractClass都可有任意多个ConcreteClass, 而每个ConcreteClass都可以给出这些抽象方法的不同实现, 从而使得顶级逻辑的功能各不相同:

class CheckOutConcreteATMBusiness extends AbstractATMBusiness {
    @Override
    protected void onBusiness() {
        System.out.println(" ... 取款");
    }
}
class ChangePasswordConcreteATMBusiness extends AbstractATMBusiness {
    @Override
    protected void onBusiness() {
        System.out.println(" ... 修改密码");
    }
}

Client

 /**
 * Created by jifang on 15/12/3.
 */
public class Client {
    @Test
    public void client() {
        AbstractATMBusiness changePassword = new ChangePasswordConcreteATMBusiness();
        changePassword.run();
        AbstractATMBusiness checkOut = new CheckOutConcreteATMBusiness();
        checkOut.run();
    }
}

实例

Servlet

HttpServlet定义了service()方法固定下来HTTP请求的整体处理流程,使得开发Servlet只需继承HttpServlet并实现doGet()/doPost()等方法完成业务逻辑处理, 并不需要关心具体的HTTP响应流程:

/**
 * 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 == -1) {
        // 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 / 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);
    }
}

详见: Servlet - 基础.

统一定时调度

将这个示例放在此处可能有些不大合适, 但它也体现了一些模板方法的思想:

1. 实现

ScheduleTaskMonitor
/**
 * @author jifang
 * @since 16/8/23 下午3:35.
 */
public class ScheduleTaskMonitor implements InitializingBean, DisposableBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskMonitor.class);
    private static final int _10S = 10_000;
    private List<ScheduleTask> tasks = new CopyOnWriteArrayList<>();
    private static final Timer timer = new Timer("ScheduleTaskMonitor");
    private void start() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                for (ScheduleTask task : tasks) {
                    task.scheduleTask();
                }
            }
        }, 0, _10S);
    }
    public void register(ScheduleTask task) {
        tasks.add(task);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        this.start();
        LOGGER.info("Start Monitor {}", this.getClass());
    }
    @Override
    public void destroy() throws Exception {
        timer.cancel();
        LOGGER.info("Stop Monitor {}", this.getClass());
    }
}
ScheduleTask
public interface ScheduleTask {
    void scheduleTask();
}

2. 使用

只需在spring的配置文件中引入该Bean:

<bean id="monitor" class="com.template.ScheduleTaskMonitor"/>

需要统一定时的类实现ScheduleTask接口, 并将自己注册到monitor中:

/**
 * @author jifang
 * @since 16/3/16 上午9:59.
 */
@Controller
public class LoginController implements ScheduleTask, InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private ScheduleTaskMonitor monitor;
    @Override
    public void scheduleTask() {
        LOGGER.error("O(∩_∩)O 日志记录~");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        monitor.register(this);
    }
}

即可完成scheduleTask()方法的定时调度.

小结

模板方法模式提供了一个很好的代码复用平台, 他通过把不变行为搬移到父类, 去除子类中重复代码来体现它的优势: 有时我们会遇到由一系列步骤构成的过程需要执行, 该过程从高层次上看是相同的, 但有某些细节的实现可能不同, 此时就可以考虑使用用模板方法了.

适用

一次性实现算法的不变部分, 并将可变的行为留给子类来实现;

各子类中公共的行为应该被提取出来并集中到一个公共父类中避免代码重复, 如: Servlet 的 service()方法.

控制子类扩展, 模板方法只在特定点调用hook操作, 这样就只允许在这些点进行扩展, 如: Junit测试框架.

相关模式

Factory Method常被模板方法调用.

Strategy: 模板方法使用继承来改变算法的一部分, Strategy使用委托来改变整个算法.

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2017-01-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术与生活

设计模式-代理模式

以上属于静态代理,比较简单。与之对应的是动态代理,在运行时间内创建代理对象,JDK提供了 Proxy 和InvokationHandler 来处理 首先构造动态...

921
来自专栏林德熙的博客

C# 金额转中文大写

创建的项目是创建一个 dot net core 的项目,实际上这个项目可以创建为 Stand 的。

1502
来自专栏微信公众号:Java团长

Java基础知识详细总结

ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类...

1293
来自专栏cs

c++14.0 名字空间和条件编译

这二个东西,经常被我们忽略了,其实很实用。当你深入c++世界的时候,总有一天会遇到他们,先面熟一下吧。 ---- 1.0 名称空间。 ---- 知识点综述: 名...

3337
来自专栏Java学习之路

02 Spring框架 简单配置和三种bean的创建方式

整理了一下之前学习Spring框架时候的一点笔记。如有错误欢迎指正,不喜勿喷。 上一节学习了如何搭建SpringIOC的环境,下一步我们就来讨论一下如何利...

3215
来自专栏爱撒谎的男孩

Struts2之获取请求参数

3466
来自专栏魂祭心

原 What Every Dev need

2838
来自专栏java一日一条

50个常见的 Java 错误及避免方法(第二部分)

System.out.println("Whatdo you want to do?");

1143
来自专栏java初学

spring框架(1)— 依赖注入

46212
来自专栏java一日一条

Java 并发开发:内置锁 Synchronized

在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程访问某一共享、可变数据时,始终都不会导致数据破坏以及其他不该出现的结果。而...

732

扫码关注云+社区

领取腾讯云代金券