何为模板方法模式

模板方法模式在一个方法中定义一个算法的骨架,而把一些步骤延迟到子类。模板方法使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤

模板方法模式属于行为型模式,其对应的类图如下所示。

template_pattern_uml_diagram

图片来源于 HeadFirst 设计模式。

举例

提到模板方法模式,我想到了炒菜。炒菜主要的步骤如下:

倒油 -> 烧油 -> 炒蒜 -> 炒菜 -> 放盐 -> 加水 -> 出锅

我们现在可以用代码来模拟炒菜,其中以上的步骤是炒菜的通用步骤,也就是说以上几步是炒菜的共性,可以将其抽取出来。抽取后的代码如下所示。

public abstract class Dish {
    
    /**
     * 炒菜步骤不能随意破坏
     */
    public final void prepareDish() {
        
        pourOil();
        boilOil();
        fry();
        addSalt();
        addWater();
        beforeServed();
        served();
        afterServed();
    }
    
    protected void pourOil() {
        System.out.println("倒入适量的油...");
    }
    
    protected void boilOil() {
        System.out.println("开始烧油...");
    }
    
    // 炒的内容由不同的菜决定
    protected abstract void fry();
    
    protected void addSalt() {
        System.out.println("加半勺盐...");
    }
    
    protected void addWater() {
        System.out.println("加50ml水...");
    }
    
    protected void served() {
        System.out.println("出锅,装碗...");
    }
    
    // 出锅之前需要做什么?(默认不做)
    protected void beforeServed() {
        
    }
    
    // 出锅之后需要做什么?(默认不做)
    protected void afterServed() {
        
    }
    
}

接下来就是毛豆炒肉了,毛豆炒肉的炒法,不过毛豆炒肉的做法比较简单。

public class EdamameStirFry extends Dish {
    @Override
    protected void fry() {
        System.out.println("倒入蒜子和青椒炒20秒...");
        System.out.println("倒肉,炒肉至炒熟...");
        System.out.println("倒入毛豆,翻炒1分钟...");
    }

    @Override
    protected void beforeServed() {
        System.out.println("加20ml生抽...");
    }
}

不过有些菜在炒的时候不需要加水,炒完即可直接出锅。比如韭菜炒蛋,韭菜在炒的时候水分就容易流出来,所以不需要加水,说起韭菜炒蛋,要先单独炒蛋,再炒韭菜,但是有些菜确实有这种分开炒的步骤,所以我们需要将抽象类中的步骤修改一下。甚至有些菜还需要焯水的步骤等等(比如西兰花)。

public abstract class Dish {
    
    protected int times = 0; // 提前出锅次数
    
    /**
     * 炒菜步骤不能随意破坏
     */
    public final void prepareDish() {
        if(isBoilingWater()) {
            boilingWater();
        }
        pourOil();
        boilOil();
        fry();
        // 当要提前出锅备用时,我们可以用钩子函数
        while(isAdvance()) {
            times++;
            served();
            pourOil();
            boilOil();
            fry();
        }
        addSalt();
        addWater();
        beforeServed();
        served();
        afterServed();
    }
    
    // 是否需要提前出锅的操作
    protected boolean isAdvance() {
        return false;
    }
    
    // 炒菜之前是否要焯水
    protected boolean isBoilingWater() {
        return false;
    }
    
    // 焯水
    protected void boilingWater() {
        
    }
    
    // 其他方法省略
    
}

韭菜炒蛋的步骤

public class EggsWithLeek extends Dish {

    @Override
    protected void fry() {
        if (times == 0) {
            System.out.println("倒入鸡蛋液...");
            System.out.println("开始翻炒30秒...");
            return;
        }

        if(times == 1) {
            System.out.println("倒入韭菜...");
            System.out.println("开始炒韭菜...");
            super.addSalt();
            System.out.println("倒入炒好的鸡蛋...");
        }
    }

    @Override
    protected void addWater() {
    }

    @Override
    protected void addSalt() {
    }

    @Override
    protected boolean isAdvance() {
        return times < 1;
    }
}

运行一下

public class TemplateTest {

    public static void main(String[] args) {
        System.out.println("毛豆炒肉-------------");
        EdamameStirFry edamameStirFry = new EdamameStirFry();
        edamameStirFry.prepareDish();

        System.out.println("韭菜炒蛋-------------");
        EggsWithLeek eggsWithLeek = new EggsWithLeek();
        eggsWithLeek.prepareDish();
    }

}

运行结果如下:

毛豆炒肉-------------
倒入适量的油...
开始烧油...
倒入蒜子和青椒炒20秒...
倒肉,炒肉至炒熟...
倒入毛豆,翻炒1分钟...
加半勺盐...
加50ml水...
加20ml生抽...
出锅,装碗...
韭菜炒蛋-------------
倒入适量的油...
开始烧油...
倒入鸡蛋液...
开始翻炒30秒...
出锅,装碗...
倒入适量的油...
开始烧油...
倒入韭菜...
开始炒韭菜...
加半勺盐...
倒入炒好的鸡蛋...
出锅,装碗...

Java 中的模板方法模式

  • sort() 方法,给对象进行排序时,排序的算法已经写好,但是排序时的两个对象的比较是不知道的,需要实现 Comparable 接口中的 compareTo() 方法来决定两个对象的大小。
  • jdbcTemplate