在现实生活中,我们经常需要做一些重复的工作,比如做饭、洗衣、整理房间等。虽然每次的任务可能不完全相同,但这些任务通常会遵循一些相似的步骤。就像做饭时,我们会先准备食材、然后烹饪、最后清理厨房。而在每个步骤的具体操作上,可能会因为菜品的不同而有所区别。

模板方法模式(Template Method)正是通过定义一个算法的“骨架”,将一些步骤的具体实现留给子类来完成。这样,父类负责控制算法的执行流程,而子类负责实现每个步骤的细节,从而达到了代码复用和灵活扩展的目的。

趣味解读:做饭的固定流程

想象你是一个大厨,每次做菜时,虽然菜的种类不同,但做菜的基本流程却是相似的。例如:

  1. 准备食材:无论做什么菜,首先要准备好食材。

  2. 烹饪:然后根据不同的菜式,使用不同的方式进行烹饪。

  3. 清理厨房:做完饭后,清理厨房是不可缺少的步骤。

虽然每道菜在“烹饪”这一步会有不同的具体做法,但其余的步骤是一样的。你作为大厨,可以通过模板方法模式来确保每道菜都遵循这个固定流程,而具体的烹饪方式由每个菜品的厨师(子类)来定义。

Java代码案例:制作饮料

假设你要开发一个饮料制作系统,其中包括不同的饮料类型,如茶和咖啡。制作这些饮料时,它们有相似的制作流程:首先烧水、然后泡茶/煮咖啡,最后倒入杯中并提供服务。每种饮料的具体泡制方式有所不同,但其余的步骤保持不变。

// 1. 饮料制作的模板类
abstract class Beverage {
    
    // 模板方法:定义饮料制作的固定步骤
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
​
    // 具体步骤:烧水
    private void boilWater() {
        System.out.println("烧开水...");
    }
​
    // 具体步骤:将饮料泡制或煮沸
    protected abstract void brew();
​
    // 具体步骤:倒入杯中
    private void pourInCup() {
        System.out.println("倒入杯中...");
    }
​
    // 钩子方法:询问顾客是否需要调味品,默认返回true
    protected boolean customerWantsCondiments() {
        return true;
    }
​
    // 具体步骤:添加调味品
    protected abstract void addCondiments();
}
​
// 2. 具体子类 - 茶
class Tea extends Beverage {
    
    @Override
    protected void brew() {
        System.out.println("用热水泡茶...");
    }
​
    @Override
    protected void addCondiments() {
        System.out.println("加柠檬...");
    }
}
​
// 3. 具体子类 - 咖啡
class Coffee extends Beverage {
    
    @Override
    protected void brew() {
        System.out.println("用热水煮咖啡...");
    }
​
    @Override
    protected void addCondiments() {
        System.out.println("加牛奶和糖...");
    }
​
    // 重写钩子方法,决定是否添加调味品
    @Override
    protected boolean customerWantsCondiments() {
        return false;  // 咖啡不加调味品
    }
}
​
// 4. 客户端代码
public class Main {
    public static void main(String[] args) {
        // 制作茶
        Beverage tea = new Tea();
        tea.prepareRecipe();
​
        System.out.println();
​
        // 制作咖啡
        Beverage coffee = new Coffee();
        coffee.prepareRecipe();
    }
}

代码解析:

  1. 模板方法 (prepareRecipe):这是一个在 Beverage 类中定义的模板方法,它规定了制作饮料的步骤流程。首先是烧水、泡制饮料、倒入杯中,然后根据需要添加调味品。

  2. 钩子方法 (customerWantsCondiments):这是一个可被子类覆盖的方法,用来决定是否在饮料中加入调味品。默认情况下它返回 true,但在 Coffee 类中,顾客选择不加调味品,所以该方法被重写返回 false

  3. 抽象方法 (brewaddCondiments)brewaddCondiments 方法是抽象的,必须由子类实现。这些方法在每种饮料的制作过程中有所不同,因此由子类提供具体实现。

  4. 具体子类 (TeaCoffee)Tea 类和 Coffee 类分别实现了 brewaddCondiments 方法,定义了茶和咖啡的具体制作步骤。Coffee 类还重写了钩子方法,决定不加调味品。

运行结果:

烧开水...
用热水泡茶...
倒入杯中...
加柠檬...
​
烧开水...
用热水煮咖啡...
倒入杯中...

实际应用场景

  1. 工作流系统:许多工作流或审批流程都有固定的步骤,比如审批、签名、归档等。模板方法模式可以帮助我们定义固定的流程框架,而在每个环节上执行不同的处理逻辑。

  2. 数据导入导出:导入导出的过程一般包括读取数据、验证数据、存储数据等固定步骤,而具体的读取和存储方式可以由子类根据不同的数据类型来实现。

  3. 游戏中的关卡流程:游戏中的每一关都可能遵循一个固定的流程,例如加载资源、创建关卡、开始战斗等,而每一关的具体实现则可能不同,模板方法可以用来规范这些流程。

总结

模板方法模式通过定义一个算法的“骨架”,将一些步骤的具体实现留给子类来完成。它的主要优点是代码复用和流程控制,使得系统更加灵活且易于维护。通过将固定步骤和变化的部分分开,模板方法模式使得代码结构更加清晰,便于管理复杂的流程。