Java案例如何实现模板方法模式?

wen java案例 57

Java案例深度解析与实战问答

目录导读

  1. 模板方法模式是什么?——定义与核心思想
  2. 为什么需要模板方法模式?——解决代码复用与扩展性痛点
  3. Java案例实战:咖啡与茶的制作流程
  4. 模板方法模式的结构解析
  5. 进阶技巧:钩子方法(Hook)的灵活运用
  6. 常见问题问答(FAQ)
  7. 总结与最佳实践

模板方法模式是什么?

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义一个操作中的算法骨架,而将某些步骤的实现延迟到子类中,通俗地说,父类定流程,子类定细节”。

Java案例如何实现模板方法模式?

核心思想

  • 在抽象类中定义一个模板方法(通常为 final,防止子类重写)。
  • 模板方法中调用一系列基本方法(抽象方法或具体方法)。
  • 子类通过实现抽象方法或重写钩子方法来定制具体行为。

一句话记忆:父类写剧本,子类演角色。


为什么需要模板方法模式?

痛点场景

假设你要实现两个功能类似的类:CoffeeMakerTeaMaker,它们都有“烧水”、“冲泡”、“倒杯”、“加调料”四个步骤,但冲泡的原料和加调料的逻辑不同,如果直接复制代码,维护成本高,且违反 DRY 原则。

模式优势

  • 代码复用:将公共算法步骤上移到父类,子类只关注差异部分。
  • 扩展性:新增饮品(如牛奶)只需新写一个子类,无需修改现有代码。
  • 控制反转:父类控制流程,子类提供实现,符合好莱坞原则(Don't call us, we'll call you)。

Java案例实战:咖啡与茶的制作流程

我们以一个经典的“饮品制作”案例来演示,假设咖啡和茶的制作步骤一致,但细节不同。

1 定义抽象类 Beverage

public abstract class Beverage {
    // 模板方法,控制制作流程(固定步骤)
    public final void prepareRecipe() {
        boilWater();        // 步骤1:烧水
        brew();             // 步骤2:冲泡(抽象方法)
        pourInCup();        // 步骤3:倒杯(具体方法)
        addCondiments();    // 步骤4:加调料(抽象方法)
    }
    // 具体方法:所有子类共享
    public void boilWater() {
        System.out.println("烧开水...");
    }
    public void pourInCup() {
        System.out.println("将饮品倒入杯中...");
    }
    // 抽象方法:子类必须实现
    public abstract void brew();
    public abstract void addCondiments();
}

2 创建具体子类

咖啡类 Coffee

public class Coffee extends Beverage {
    @Override
    public void brew() {
        System.out.println("用沸水冲泡咖啡粉...");
    }
    @Override
    public void addCondiments() {
        System.out.println("加入糖和牛奶...");
    }
}

茶类 Tea

public class Tea extends Beverage {
    @Override
    public void brew() {
        System.out.println("用沸水浸泡茶叶...");
    }
    @Override
    public void addCondiments() {
        System.out.println("加入柠檬...");
    }
}

3 客户端测试

public class Client {
    public static void main(String[] args) {
        System.out.println("===== 制作咖啡 =====");
        Beverage coffee = new Coffee();
        coffee.prepareRecipe();
        System.out.println("\n===== 制作茶 =====");
        Beverage tea = new Tea();
        tea.prepareRecipe();
    }
}

输出结果

===== 制作咖啡 =====
烧开水...
用沸水冲泡咖啡粉...
将饮品倒入杯中...
加入糖和牛奶...
===== 制作茶 =====
烧开水...
用沸水浸泡茶叶...
将饮品倒入杯中...
加入柠檬...

模板方法模式的结构解析

类图结构(UML)

+----------------+       +----------------+
| AbstractClass  |       | ConcreteClass  |
|----------------|       |----------------|
| +templateMethod()|       | +primitiveOp1()|
| +primitiveOp1() |       | +primitiveOp2()|
| +primitiveOp2() |       +----------------+
| +hookMethod()   |
+----------------+

角色说明

  • 抽象类:定义模板方法,包含基本方法(抽象、具体、钩子)。
  • 具体类:实现抽象方法和钩子方法,但不改变流程。
  • 钩子方法:可选实现的“空方法”,子类可以重写以影响流程(如是否加调料)。

进阶技巧:钩子方法(Hook)的灵活运用

钩子方法允许子类条件性地控制模板方法的某一步骤,某些客户可能不希望加任何调料。

增加钩子方法

修改 Beverage 抽象类:

public abstract class Beverage {
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        // 钩子方法:判断是否加调料
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
    // 默认钩子:返回 true
    public boolean customerWantsCondiments() {
        return true;
    }
    // 其余方法同前...
}

子类重写钩子

public class Coffee extends Beverage {
    // 其他方法同前...
    @Override
    public boolean customerWantsCondiments() {
        String answer = getUserInput(); // 模拟用户输入
        return answer.equalsIgnoreCase("y");
    }
    private String getUserInput() {
        // 简化实现,实际可读取控制台
        return "n"; // 假设用户说“不需要”
    }
}

现在运行 prepareRecipe(),如果用户输入“n”,则不会执行 addCondiments()


常见问题问答(FAQ)

Q1:模板方法模式与策略模式有什么区别?
A:两者都封装了算法,但侧重点不同。

  • 模板方法:通过继承在父类中定义算法骨架,子类重写部分步骤,适用于“算法结构固定,但步骤可变化”。
  • 策略模式:通过组合将算法封装成独立对象,客户端可动态切换算法,适用于“算法整体可替换”。

Q2:模板方法是否必须是 final 的?
A:推荐用 final 修饰,防止子类无意中覆盖模板方法,破坏算法骨架,Java 官方也建议将模板方法声明为 final。

Q3:钩子方法是否必须实现?
A:不必,钩子方法在抽象类中提供默认实现(空方法或返回固定值),子类可以选择性重写,钩子方法的主要作用是让子类对模板方法的执行施加影响,而不必实现所有抽象方法。

Q4:模板方法模式在 JDK 中有哪些应用?
A:常见的有:

  • java.util.AbstractList:定义了 addAll()indexOf() 等模板方法。
  • java.io.InputStreamread() 方法是模板方法,子类只需实现 read(byte[], int, int)
  • javax.servlet.http.HttpServletdoGet()doPost() 等是模板方法中的钩子,由 service() 调用。

Q5:如何避免模板方法导致类爆炸?
A:

  • 合理设计抽象类,将共性尽可能上移。
  • 结合工厂方法或策略模式,将变化点局部化。
  • 使用组合而非继承(如策略模式)来替代部分模板方法的职责。

总结与最佳实践

适用场景

  • 多个子类有相同的算法流程,但某些步骤的实现不同。
  • 需要控制子类的扩展点,防止子类改变算法结构。
  • 在框架设计中,通过模板方法提供“模板式”的扩展能力(如 Spring 的 JdbcTemplate)。

实践建议

  1. 抽象类中尽量多用 final 方法,尤其是模板方法本身。
  2. 钩子方法不要过多,否则会增加子类的复杂度。
  3. 将不变的部分上移,可变的部分下放——这符合开闭原则。
  4. 命名约定:模板方法通常以 templateexecuteprocess 开头,钩子方法以 hookbeforeafter 开头。

最后思考

模板方法模式通过“反向控制”实现了代码的高内聚与低耦合,是应对重复逻辑且流程固定的最优雅方案之一,如果你在做 Java 框架开发或处理批量业务规则时,不妨先问问自己:“这个流程的骨架是固定的吗?” 如果是,模板方法就是你的最佳伙伴。

抱歉,评论功能暂时关闭!