模板方法模式

简介: 模板方法模式标签 : Java与设计模式 模板方法模式: 定义一个操作中的算法的骨架, 而将一些步骤延迟到子类中.

模板方法模式

标签 : 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()方法的定时调度.


小结

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

  • 适用

    • 一次性实现算法的不变部分, 并将可变的行为留给子类来实现;
    • 各子类中公共的行为应该被提取出来并集中到一个公共父类中避免代码重复, 如: Servletservice()方法.
    • 控制子类扩展, 模板方法只在特定点调用hook操作, 这样就只允许在这些点进行扩展, 如: Junit测试框架.
  • 相关模式

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

参考 & 扩展
设计模式:可复用面向对象软件的基础
大话设计模式
高淇将设计模式

目录
相关文章
|
消息中间件 缓存 NoSQL
谈谈高并发系统的设计方法论
设计 `高并发` 系统,就是要让该系统保证它 `整体可用` 的同时,能够尽可能多的 `处理很高的并发用户请求`,能够 `承受很大的负载流量冲击`。
1403 6
|
监控 druid Java
监控druid数据库连接池连接泄露的思路
监控druid数据库连接池连接泄露的思路
1690 2
dataframe获取指定列
dataframe获取指定列
1397 0
|
SQL 缓存
mybatisplus分页查询——Page
(2)各个参数的含义 (1)records:用来存放查询出来的数据 (2)total: 用来返回记录的总数 (3)size: 每页显示条数,默认 10 (4)current:表示当前页,默认1 (5)orders: 排序字段信息 (6)optimizeCountSql: 自动优化 COUNT SQL,默认true (7)isSearchCount: 是否进行 count 查询,默认true (8)hitCount: 是否命中count缓存,默认false
1637 0
|
7月前
|
机器学习/深度学习 存储 缓存
加速LLM大模型推理,KV缓存技术详解与PyTorch实现
大型语言模型(LLM)的推理效率是AI领域的重要挑战。本文聚焦KV缓存技术,通过存储复用注意力机制中的Key和Value张量,减少冗余计算,显著提升推理效率。文章从理论到实践,详细解析KV缓存原理、实现与性能优势,并提供PyTorch代码示例。实验表明,该技术在长序列生成中可将推理时间降低近60%,为大模型优化提供了有效方案。
1394 15
加速LLM大模型推理,KV缓存技术详解与PyTorch实现
|
10月前
|
存储 弹性计算 架构师
老板点赞!技术人如何用架构优化打赢降本增效战?
大家好,我是小米,一个喜欢分享技术的小架构师。通过亲身经历,我将介绍如何通过架构优化帮助公司降本增效。两年前,我加入一家初创公司,面对成本高企的问题,通过弹性伸缩、微服务化和数据治理等手段,成功降低了40%的技术成本,提升了60%的系统响应速度。希望我的经验能给你启发!关注我的微信公众号“软件求生”,获取更多技术干货。
202 5
|
Go
Golang语言基础之接口(interface)及类型断言
这篇文章是关于Go语言中接口(interface)及类型断言的详细教程,涵盖了接口的概念、定义、实现、使用注意事项以及类型断言的多种场景和方法。
420 4
|
NoSQL 关系型数据库 OLAP
如何选择最合适的数据库,帮助企业及个人业务更好的开展
如何选择最合适的数据库,帮助企业及个人业务更好的开展
|
Apache 开发者 Java
Apache Wicket揭秘:如何巧妙利用模型与表单机制,实现Web应用高效开发?
【8月更文挑战第31天】本文深入探讨了Apache Wicket的模型与表单处理机制。Wicket作为一个组件化的Java Web框架,提供了多种模型实现,如CompoundPropertyModel等,充当组件与数据间的桥梁。文章通过示例介绍了模型创建及使用方法,并详细讲解了表单组件、提交处理及验证机制,帮助开发者更好地理解如何利用Wicket构建高效、易维护的Web应用程序。
248 0
|
JavaScript
Vue3基础(21)___在axios.js中使用路由跳转
本文介绍了在Vue 3中如何在axios.js中使用路由跳转,通过直接引入路由实例并使用`router.push`实现页面跳转。
490 0