👉文章示例代码👈

定义

定义一个算法的步骤,并允许子类为一个或者多个步骤提供实现。

模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

场景示例

笔者这里以做番茄炒蛋为例。笔者将做番茄炒蛋的步骤分为以下几步:倒油、放鸡蛋、放番茄、倒调料、翻炒。不考虑多余的细节问题,不同的人做番茄炒蛋的步骤应该是类似的。

创建抽象模板

这里主要定义炒菜的步骤。

/**
 * @author zhh
 * @description 抽象模板类
 * @date 2020-02-27 15:47
 */
public abstract class AbstractCook {

    /**
     * 做饭的整体步骤
     */
    protected final void cook() {
        this.pourOil();
        this.addEgg();
        this.addTomato();
        this.pourSeasoning();
        this.stirFry();
        if (needChoppedGreenOnion()) {
            this.addChoppedGreenOnion();
        }
    }

    /**
     * 倒油
     */
    final void pourOil() {
        System.out.println("倒入食用油");
    }

    /**
     * 放鸡蛋
     */
    final void addEgg() {
        System.out.println("放入鸡蛋");
    }

    /**
     * 放番茄
     */
    final void addTomato() {
        System.out.println("放入番茄");
    }

    /**
     * 翻炒
     */
    final void stirFry() {
        System.out.println("快速翻炒");
    }

    /**
     * 放葱花
     */
    final void addChoppedGreenOnion() {
        System.out.println("放点葱花");
    }

    /**
     * 是否需要葱花 (钩子方法)
     */
    protected boolean needChoppedGreenOnion() {
        return false;
    }

    /**
     * 倒调味品
     */
    abstract void pourSeasoning();
}

这里的 ​needChoppedGreenOnion() 是一个钩子方法,让具体的子类来决定是否添加葱花,默认不添加。

以上的部分方法用 ​final 修饰,是为了防止子类对其进行重写。

创建具体子类

这里以笔者做饭和女朋友做饭为例。由于两人的口味不同,倒入的调味品会存在些许差异。而且笔者喜欢在番茄炒蛋中加入葱花。

/**
 * @author zhh
 * @description 具体子类
 * @date 2020-02-27 16:04
 */
public class MeCook extends AbstractCook {

    @Override
    void pourSeasoning() {
        System.out.println("放点盐");
        System.out.println("放点酱油");
    }

    /**
     * 此处覆盖了父类钩子方法的默认实现
     */
    @Override
    protected boolean needChoppedGreenOnion() {
        return true;
    }
}


/**
 * @author zhh
 * @description 具体子类
 * @date 2020-02-27 16:07
 */
public class GirlFriendCook extends AbstractCook {

    @Override
    void pourSeasoning() {
        System.out.println("放点盐");
    }
}

我们可以看到在 ​MeCook 类中重写了父类的 ​needChoppedGreenOnion() 方法,那么对于父类中模板方法执行 ​needChoppedGreenOnion() 方法时,拿到的其实是子类方法的返回值。

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2020-02-27 16:13
 */
public class Test {

    public static void main(String[] args) {
        // 我做番茄炒蛋
        System.out.println("---我做番茄炒蛋 开始---");
        AbstractCook meCook = new MeCook();
        meCook.cook();
        System.out.println("---我做番茄炒蛋 结束---");

        // 女朋友做番茄炒蛋
        System.out.println("---女朋友做番茄炒蛋 开始---");
        AbstractCook girlFriendCook = new GirlFriendCook();
        girlFriendCook.cook();
        System.out.println("---女朋友做番茄炒蛋 结束---");
    }
}

测试类输出的结果如下:

—我做番茄炒蛋 开始—

倒入食用油

放入鸡蛋

放入番茄

放点盐

放点酱油

快速翻炒

放点葱花

—我做番茄炒蛋 结束—

—女朋友做番茄炒蛋 开始—

倒入食用油

放入鸡蛋

放入番茄

放点盐

快速翻炒

—女朋友做番茄炒蛋 结束—

类结构图

以上示例类的结构图如下所示

image.png

总结

适用场景

  • 算法的整体步骤固定,但其中的个别部分易变。
  • 多个子类存在公共的行为,可以将其提取出来并且集中到一个公共父类当中,避免代码重复。

优点

  • 父类提供公共部分代码,提高复用性。
  • 将不变部分的算法封装到父类中实现,把可变部分的算法由子类继承去实现,提高了扩展性。

缺点

  • 对于每个不同的实现都需要一个子类来实现,导致类数目的增加,增加系统实现的复杂度。
  • 父类中的抽象方法由自子类实现,如果父类添加新的抽象方法,所有的子类都需要修改。

扩展

钩子方法:提供缺省行为,子类可以在必要时进行扩展。

参考