设计模式学习(15) 23-13 模版方法模式

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析](#5. 源码分析)

0.个人感悟

  • 个人对这个模式印象很深,属于是工作中自己无意识地用到过,后面看书才发现原来已有前辈总结出这个模式,并且更加全面
  • 模版方法模式真的是很典型、很实用,建议大家可以在代码中尝试尝试
  • 模版方法整体是基于继承机制的代码复用,有利于感受抽象、封装、继承、多态的魅力
  • 模版方法模式很好地展现了开闭原则和实现开闭地一个方式: 将变化(具体实现)与不变的部分(算法骨架)分离

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

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 chaging the algorithm's structure.

中文翻译

定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。

理解

  • 模板方法模式是一种行为型设计模式
  • 父类(抽象类) 定义了一个算法的固定框架和流程(模板方法)
  • 具体步骤 的实现延迟到子类中完成
  • 子类可以重写某些特定步骤 ,但不能改变算法的整体结构
  • 通过钩子方法(Hook Method) 提供额外的扩展点,允许子类对算法的某些部分进行可选的重写

2. 适配场景

2.1 适合的场景

  • 当多个类有相同的方法流程 ,但某些步骤的实现不同时,需要提取公共行为到父类,避免代码重复
  • 存在固定的算法骨架 ,但具体步骤需要灵活变化,需要控制子类的扩展方式,确保算法结构不被破坏

2.2 常见场景举例

  • 制作饮料:冲泡咖啡、茶的流程相似(烧水、冲泡、加调料),但具体步骤不同
  • 数据导入处理:读取数据、验证数据、处理数据、保存数据的流程固定,但不同数据源处理方式不同
  • Spring框架的JdbcTemplate:连接数据库、执行SQL、处理结果集、关闭连接的流程固定
  • 各种初始化流程:游戏初始化、应用启动、设备自检等有固定步骤但细节不同的场景

3. 实现方法

3.1 实现思路

  1. 创建抽象类:定义算法的骨架
  2. 定义模板方法:使用final修饰,防止子类改变算法结构
  3. 定义抽象方法:子类必须实现的具体步骤
  4. 定义具体方法:父类提供的默认实现,子类可选择是否重写
  5. 定义钩子方法:提供默认实现,子类可选择重写以影响算法流程
  6. 创建具体子类:实现抽象方法,可选重写钩子方法

3.2 UML类图

角色说明

  • AbstractClass(抽象类):定义模板方法和算法骨架
  • ConcreteClass(具体子类):实现抽象步骤,可选重写钩子方法

3.3 代码示例

制作饮料,冲泡咖啡、茶的流程相似(烧水、冲泡、加调料),但具体步骤不同

定义模版:

java 复制代码
public abstract class BeverageTemplate {  
    // 定义算法骨架 final防止子类篡改  
  
    /**  
     * @description 制作饮料  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     public final void prepareBeverage() {  
  
        boilWater();  
        brew();  
        pourInCup();  
        if (customerWantsCondiments()) {  
            addCondiments();  
        }  
        hook();  
    }  
  
  
    // 通用流程  
  
    /**  
     * @description 烧水  
     * @author bigHao  
     * @date 2026/1/20  
     **/    private void boilWater() {  
        System.out.println("烧水");  
    }  
  
  
    // 通用流程  
  
    /**  
     * @description 倒饮料  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     private void pourInCup() {  
        System.out.println("将饮料倒入杯子");  
    }  
  
  
    // 特异流程,子类实现  
  
    /**  
     * @description 酿造  
     * @author bigHao  
     * @date 2026/1/20  
     **/  
    protected abstract void brew();  
  
  
    // 特异流程,子类实现  
  
    /**  
     * @description 添加作料  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     protected abstract void addCondiments();  
  
    // 钩子方法-子类可以重写,用于流程控制  
  
    /**  
     * @description 钩子方法  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     protected void hook() {  
        // 默认空实现  
    }  
  
    // 钩子方法,用于流程控制,子类可以重写  
  
    /**  
     * @return boolean 是否需要  
     * @description 客户需要作料  
     * @author bigHao  
     * @date 2026/1/20  
     **/    
     protected boolean customerWantsCondiments() {  
        return true;  
    }  
}

具体实现:

java 复制代码
public class Coffee extends BeverageTemplate {  
    @Override  
    protected void brew() {  
        System.out.println("沸水冲泡咖啡");  
    }  
  
    @Override  
    protected void addCondiments() {  
        System.out.println("加入糖和牛奶");  
    }  
  
    @Override  
    protected void hook() {  
        super.hook();  
        System.out.println("hook");  
    }  
}

public class Tea extends BeverageTemplate {  
    @Override  
    protected void brew() {  
        System.out.println("沸水煮茶叶");  
    }  
  
    @Override  
    protected void addCondiments() {  
        System.out.println("加入柠檬");  
    }  
}

测试:

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("=== 制作coffee ===");  
        BeverageTemplate coffee = new Coffee();  
        coffee.prepareBeverage();  
  
        System.out.println("=== 制作tea ===");  
        BeverageTemplate tea = new Tea();  
        tea.prepareBeverage();  
    }  
}

输出:

复制代码
=== 制作coffee ===
烧水
用非税冲泡咖啡
将饮料倒入杯子
加入糖和牛奶
hook
=== 制作tea ===
烧水
沸水煮茶叶
将饮料倒入杯子
加入柠檬

4. 优缺点

4.1 优点

  • 提高代码复用性:将公共行为提取到父类,避免代码重复
  • 良好的扩展性:通过增加新的子类可以轻松扩展算法
  • 符合开闭原则:对扩展开放(新增子类),对修改关闭(不修改模板方法)
  • 提高可维护性:算法结构清晰,便于理解和维护
  • 封装不变部分:将不变的行为封装在父类,可变行为由子类实现

4.2 缺点

  • 可能导致类数量增加:每个不同的实现都需要一个子类
  • 父类与子类耦合:子类必须实现父类的抽象方法
  • 限制了子类的灵活性:子类不能改变算法的整体结构
  • 继承的固有缺点:Java单继承限制,子类无法继承其他类
  • 调试困难:模板方法中的流程控制可能使调试变得复杂

5. 源码分析

Spring中的HttpServlet类service()方法分析

java 复制代码
// javax.servlet.http.HttpServlet 中的模板方法模式应用
public abstract class HttpServlet extends GenericServlet {
    
    // 模板方法
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String method = req.getMethod();
        
        if (method.equals("GET")) {
            // 调用具体方法
            doGet(req, resp);
        } else if (method.equals("POST")) {
            doPost(req, resp);
        } else if (method.equals("PUT")) {
            doPut(req, resp);
        }
        // ... 其他HTTP方法
    }
    
    // 钩子方法 - 子类需要实现具体处理逻辑
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 默认实现:返回405错误(方法不允许)
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }
    
    // ... 其他HTTP方法对应的钩子方法
}

分析

  1. 模板方法service()方法定义了HTTP请求处理的整体流程
  2. 具体步骤:根据HTTP方法类型(GET、POST等)分发到不同的处理方法
  3. 钩子方法doGet()doPost()等由子类实现具体的业务逻辑
  4. 默认实现:父类提供错误响应的默认实现
  5. 扩展方式:开发者继承HttpServlet,只需重写需要处理的HTTP方法

使用示例

java 复制代码
@WebServlet("/user")
public class UserServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 处理GET请求:查询用户信息
        // ...
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 处理POST请求:创建用户
        // ...
    }
}

参考:

相关推荐
茶本无香1 小时前
设计模式之四:建造者模式(Builder Pattern)详解
java·设计模式·建造者模式
毕设源码-赖学姐1 小时前
【开题答辩全过程】以 高校素拓分管理系统的设计与开发为例,包含答辩的问题和答案
java·eclipse
计算机学姐2 小时前
基于SpringBoot的社区互助系统
java·spring boot·后端·mysql·spring·信息可视化·推荐算法
lbb 小魔仙2 小时前
【Java】深入解析 Java 集合底层原理:HashMap 扩容与 TreeMap 红黑树实现
java·开发语言
June bug2 小时前
【配环境】安装配置Oracle JDK
java·数据库·oracle
非凡ghost2 小时前
批量转双层PDF(可识别各种语言)
windows·学习·pdf·软件需求
开开心心_Every2 小时前
网络管理员IP配置工具:设置多台电脑地址
运维·服务器·网络·网络协议·学习·tcp/ip·edge
Coder个人博客2 小时前
1233434235
java·开发语言
知识分享小能手2 小时前
Oracle 19c入门学习教程,从入门到精通,Oracle 控制文件与日志文件管理详解(8)
数据库·学习·oracle