目录

情景

泡茶和冲咖啡,代码如下:

public class Coffee {
    public void prepareRecipe(){
        boilWater();
        makeCoffee();
        pourInCup();
        addSugarAndMilk();
    }
    public void boilWater(){
        System.out.println("正在烧水");
    }
    public void addSugarAndMilk(){
        System.out.println("正在加糖和牛奶");
    }
    public void pourInCup(){
        System.out.println("正在将咖啡倒进杯子");
    }
    public void makeCoffee(){
        System.out.println("正在用沸水煮咖啡");
    }
}

public class Tea {
    public void prepareRecipe(){
        boilWater();
        makeTea();
        pourInCup();
        addLemon();
    }
    public void boilWater(){
        System.out.println("正在烧水");
    }
    public void addLemon(){
        System.out.println("正在加柠檬");
    }
    public void pourInCup(){
        System.out.println("正在将茶倒进杯子");
    }
    public void makeTea(){
        System.out.println("正在用沸水泡茶");
    }
}

模板方法

上述重复代码,如何复用?重新设计如下:

/**
 * final 方法不希望子类去覆盖
 * 抽象方法延迟到子类中实现
 */
public abstract class CaffeineBeverage {
    /*
     * 模板方法
     */
    final public void prepareRecipe(){
        boilWater();
        makeIt();
        pourInCup();
        addCondiments();
    }
    final public void boilWater(){
        System.out.println("正在烧水");
    }
    final public void pourInCup(){
        System.out.println("正在将咖啡倒进杯子");
    }
    public abstract void addCondiments();
    public abstract void makeIt();
}

public class Coffee extends CaffeineBeverage{
    @Override
    public void addCondiments() {
        System.out.println("正在加糖和牛奶");
    }
    @Override
    public void makeIt() {
        System.out.println("正在用沸水煮咖啡");
    }
}

public class Tea extends CaffeineBeverage{
    @Override
    public void addCondiments() {
        System.out.println("正在加柠檬");
    }

    @Override
    public void makeIt() {
        System.out.println("正在用沸水泡茶");
    }
}
public class Test {
    public static void main(String[] args) {
        CaffeineBeverage coffee = new Coffee();
        coffee.prepareRecipe();
        System.out.println("-----");
        CaffeineBeverage tea = new Tea();
        tea.prepareRecipe();
    }
}

使用钩子

现在增加一个需求,喝咖啡的顾客可以自主选择是否需要调料。这时就用到了钩子。

代码做一下修改:

public abstract class CaffeineBeverage {
    final public void prepareRecipe(){
        boilWater();
        makeIt();
        pourInCup();
        if(customWantsCondiments()){
            addCondiments();
        }
    }
    final public void boilWater(){
        System.out.println("正在烧水");
    }
    final public void pourInCup(){
        System.out.println("正在将咖啡倒进杯子");
    }
    public abstract void addCondiments();
    public abstract void makeIt();
    public boolean customWantsCondiments(){
        // 默认认为顾客是想要调料的
        return true;
    }
}

public class CoffeeWithHook extends CaffeineBeverage{
    @Override
    public boolean customWantsCondiments() {
        return getUserInput();
    }
    @Override
    public void addCondiments() {
        System.out.println("正在加糖和牛奶");
    }
    @Override
    public void makeIt() {
        System.out.println("正在用沸水煮咖啡");
    }
    private boolean getUserInput(){
        String answer = null;
        System.out.println("Would you like sugar and milk with your coffee (y/n) ?");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try{
            answer = in.readLine();
        }catch(Exception e){
            System.out.println("IO error to read your answer");
        }
        if(answer.startsWith("y")){
            return true;
        }else{
            return false;
        }
    }
}

public class Tea extends CaffeineBeverage{
    @Override
    public void addCondiments() {
        System.out.println("正在加柠檬");
    }

    @Override
    public void makeIt() {
        System.out.println("正在用沸水泡茶");
    }
}

总结

【定义】已经定义了算法骨架,而将一些步骤延迟到子类中。

【本质】固定算法结构。

【优点】

  • 代码复用性高。
  • 容易扩展。
  • 基类专注算法架构,子类专注实现,架构清晰。
  • 比较灵活,子类可以使用钩子方法对父类算法进行反向约束。

【缺点】

模板类和子类耦合,变更算法骨架需谨慎。

模版方法的应用

  • Spring 中有太多地方使用了。

工作需要定时从一些网站上抓取数据存储到数据库中,因此创建了一个抽象类 Job,类中定义算法骨架:

  1. 抽取数据。
  2. 分析数据。
  3. 存储有用信息。
  4. 包括处理异常,如果抓取失败,按照既定策略进行重试。

前三个方法是抽象方法,每个子类的实现逻辑千差万别。

版权声明:本文为xmsx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xmsx/p/9758992.html