欢迎光临
我们一直在努力

Java 设计模式・模板方法模式篇:从思想到代码实现

一、行为型模式

在面向对象的世界里,如何优雅地组织对象间的交互、分配职责,是每一位开发者都会反复思考的问题。直接硬编码交互逻辑固然简单,但当业务复杂度上升、对象协作关系变得错综复杂时,这种方式就会让代码变得僵化、难以扩展。

行为型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何定义对象之间的通信方式和职责分配,通过命令、迭代、观察者、策略等手段,让对象间的协作更具灵活性、可复用性和可维护性。

在 Java 开发中,行为型模式主要包含以下 11 种经典实现:

  • 模板方法模式 (Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 策略模式 (Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,让算法独立于使用它的客户而变化。
  • 命令模式 (Command):将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,支持可撤销操作。
  • 责任链模式 (Chain of Responsibility):将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求,形成一条处理链。
  • 状态模式 (State):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
  • 观察者模式 (Observer):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
  • 中介者模式 (Mediator):用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而降低耦合。
  • 迭代器模式 (Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
  • 访问者模式 (Visitor):表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
  • 备忘录模式 (Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复。
  • 解释器模式 (Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  • 二、模板方法模式

    2.1 介绍

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

    2.2 角色

    • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

      • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。为防止恶意操作,一般模板方法都加上 final 关键词

      • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

        • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

        • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

        • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

    • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

    三、代码实现

    为了方便理解,本文采用中文定义类名

    3.1 抽象类

    public abstract class 吃饭模板 {
    /**
    * 模板方法
    */
    public final void eat(){
    //第一步:点餐
    order();
    //第二步:备注
    remark();
    //第三步:吃东西
    eatFood();
    //第四步:结账
    pay();
    }

    /**
    * 具体方法
    */
    private void order() {
    System.out.println("开始点餐……");
    }

    /**
    * 钩子方法
    */
    protected void remark(){}

    /**
    * 抽象方法
    */
    protected abstract void eatFood();

    /**
    * 具体方法
    */
    private void pay(){
    System.out.println("结账……");
    }
    }

    3.2 具体子类

    public class 具体吃饭 extends 吃饭模板{
    @Override
    protected void eatFood() {
    System.out.println("吃面……");
    }

    @Override
    protected void remark(){
    System.out.println("备注:吃面要加辣");
    }
    }

    四、优缺点

    4.1 优点

    • 代码复用:将相同的算法骨架和固定步骤放在抽象父类中,避免在多个子类中重复编写相同代码。
    • 流程标准化:通过 final 修饰的模板方法,确保算法的整体结构不被破坏,保证了流程的一致性和稳定性。
    • 易于扩展:新增业务场景时,只需新增子类并实现抽象的可变步骤,无需修改原有代码,符合开闭原则。
    • 职责清晰:将不变的行为移到父类,可变的行为留给子类实现,分离了职责,便于维护。

    4.2 缺点

    • 类数量膨胀:每增加一个新的业务变体,就需要新增一个子类,当可变步骤过多时,会导致类的数量急剧增加。
    • 继承限制:模板方法模式依赖于继承机制,而 Java 是单继承的,这在一定程度上限制了类的设计灵活性。
    • 反向控制:子类的执行结果会影响父类的行为(“好莱坞原则”:Don't call us, we'll call you),这可能会让代码逻辑变得不那么直观,增加理解难度。
    • 维护成本:当抽象类中的模板方法需要调整时,所有继承它的子类都会受到影响,维护成本较高。

    五、适用场景

    • 有固定流程的业务场景

    • 框架与类库设计

    • 需要控制算法步骤顺序的场景

    六、对比学习

    6.1 对比工厂方法模式

    Java 设计模式・工厂模式篇:从思想到代码实现-CSDN博客

    两者都属于 “好莱坞原则”(别找我们,我们找你)的典型应用,且都基于继承,但关注点不同。

    维度模板方法模式工厂方法模式 (Factory Method)
    核心关注点 定义执行流程(怎么做)。 定义对象创建(做什么)。
    在父类中的角色 父类调用子类的方法来完成某一步骤。 父类调用子类的方法来创建对象。
    关系 工厂方法模式本质上是模板方法模式的一种特殊应用(模板方法用于创建对象)。
    案例 JdbcTemplate (执行查询流程) Collection.iterator() (父类定义规范,子类创建具体迭代器)

    6.2 对比建造者模式

    Java 设计模式・建造者模式篇:从思想到代码实现-CSDN博客

    两者都涉及 “步骤”,但目的完全不同。

    维度模板方法模式建造者模式 (Builder)
    目的 完成一个复杂的过程 / 算法。 构建一个复杂的产品对象。
    步骤性质 步骤是算法的执行顺序,通常有依赖关系。 步骤是组装部件,通常可以调整顺序。
    执行者 由父类模板方法自动调度。 由导演类(Director)或客户端手动调用。

    七、源码举例

    7.1 InputStream

    public abstract class InputStream implements Closeable {

    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
    Objects.checkFromIndexSize(off, len, b.length);
    if (len == 0) {
    return 0;
    }

    int c = read();
    if (c == -1) {
    return -1;
    }
    b[off] = (byte)c;

    int i = 1;
    try {
    for (; i < len ; i++) {
    c = read();
    if (c == -1) {
    break;
    }
    b[off + i] = (byte)c;
    }
    } catch (IOException ee) {
    }
    return i;
    }

    }

    无参的 read() 方法是抽象方法,要求子类必须实现, 而 read(byte b[], int off, int len) 方法 调用了无参的抽象的 read() 方法。

    7.2 Spring

    AbstractApplicationContext(Spring 容器初始化)

    Spring 容器的初始化流程完全基于模板方法:

    • 核心逻辑:AbstractApplicationContext 的 refresh() 方法是模板方法,定义了容器初始化的完整流程,将 beanFactory 初始化、Bean 实例化等部分步骤延迟到子类。
    • 关键代码片段:

    public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    // 模板方法:容器初始化的完整骨架
    public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
    // 1. 准备刷新(固定步骤)
    prepareRefresh();
    // 2. 获取 BeanFactory(抽象步骤,由子类实现)
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    // 3. 准备 BeanFactory(固定步骤)
    prepareBeanFactory(beanFactory);
    try {
    // 4. 后置处理 BeanFactory(可选步骤,子类可重写)
    postProcessBeanFactory(beanFactory);
    // 5. 执行 BeanFactoryPostProcessor(固定步骤)
    invokeBeanFactoryPostProcessors(beanFactory);
    // 6. 注册 BeanPostProcessor(固定步骤)
    registerBeanPostProcessors(beanFactory);
    // 7. 初始化 MessageSource(固定步骤)
    initMessageSource();
    // … 省略其他固定步骤
    // 8. 刷新完成(固定步骤)
    finishRefresh();
    } catch (BeansException ex) {
    // 异常处理(固定步骤)
    destroyBeans();
    cancelRefresh(ex);
    throw ex;
    }
    }
    }

    // 抽象步骤:由子类(如 ClassPathXmlApplicationContext)实现
    protected abstract ConfigurableListableBeanFactory obtainFreshBeanFactory();
    }

    八、其他相关设计模式

    Java 设计模式・总结目录篇:从思想到代码实现-CSDN博客

    赞(0)
    未经允许不得转载:171主机测评 » Java 设计模式・模板方法模式篇:从思想到代码实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址