《Head First 设计模式》读书笔记——模板方法模式

模板方法模式。

案例

泡茶和冲咖啡的步骤很相似,咖啡的步骤:

  1. 把水煮沸
  2. 用沸水冲泡咖啡
  3. 把咖啡倒进杯子里
  4. 加糖和牛奶

泡茶的步骤:

  1. 把水煮沸
  2. 用沸水浸泡茶叶
  3. 把茶倒进杯子
  4. 加柠檬

所以咖啡的代码如下:

public class Coffee {

    void prepareRecipe() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }

    public void boilWater() {
        System.out.println("Boiling water");
    }

    public void brewCoffeeGrinds() {
        System.out.println("Dripping Coffee through filter");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup");
    }

    public void addSugarAndMilk() {
        System.out.println("Adding Sugar and Milk");
    }
}

茶的代码:

public class Tea {
    
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    public void boilWater() {
        System.out.println("Boiling water");
    }

    public void steepTeaBag() {
        System.out.println("Steeping the tea");
    }
    
    public void addLemon() {
        System.out.println("Adding Lemon");
    }
    
    public void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

这很明显的重复代码,所以我们可以提取公共的部分:

  1. 把水煮沸
  2. 用热水泡咖啡或茶
  3. 把饮料倒进杯子
  4. 在饮料内加入适当的调料

第一步和第三步是公共的,可以抽出来,而第二步和第四步不一样的地方也只有饮料或调料不一样而已。

我们先把 1、3 两步抽取到超类中。

public abstract class CaffeineBeverage {
    
    public abstract void prepareRecipe();

    public void boilWater() {
        System.out.println("Boiling water");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

但 2、4 两步也是很相似的,所以我们也在超类中定义,修改后再看。

public abstract class CaffeineBeverage {

    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    protected abstract void addCondiments();

    protected abstract void brew();

    public void boilWater() {
        System.out.println("Boiling water");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

我们把 2、4 两步抽象化,等子类去实现他们的具体功能。改造后的茶和咖啡如下。

public class Tea extends CaffeineBeverage {

    @Override
    protected void addCondiments() {
        System.out.println("Adding Lemon");
    }

    @Override
    protected void brew() {
        System.out.println("Steeping the tea");
    }
}

public class Coffee extends CaffeineBeverage {

    @Override
    protected void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }

    @Override
    protected void brew() {
        System.out.println("Dripping Coffee through filter");
    }
}

茶和咖啡都是继承自咖啡因饮料,他们都需要自己实现 brew() 和 addCondiments() 方法。这就是模板方法模式,preareRecipe() 是我们的模板方法,它被用作一个算法的模板,这个算法指的是制作咖啡因饮料的。算法的每一个步骤都被一个方法代表了。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

扫描二维码关注公众号,回复: 10078495 查看本文章

定义

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

其实就是抽出几个相似的类中具有的类似的步骤,这些步骤都可以是抽象的,由子类实现,这可以确保算法的结构保持不变,同时由子类提供部分实现。

我们也可以进行钩子的设定,声明到我们的抽象类里中空的或默认实现的方法。可以留给子类扩展空间,让子类有能力对算法的不同点进行不一样的步骤,具体要不要挂钩,由子类决定。下面我们先是一个例子:

public abstract class CaffeineBeverageWithHook {
    
    void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    private boolean customerWantsCondiments() {
        return true;
    }

    protected abstract void addCondiments();

    protected abstract void brew();

    public void boilWater() {
        System.out.println("Boiling water");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

我们加上了一个条件语句,条件是由 customerWantsCondiments() 方法的返回值决定的。如果顾客想要调料,返回 true,执行 addCondiments() 方法,这就是一个钩子,子类可以覆盖这个方法,但不见得必须覆盖。我们继续修改我们的代码:

public class CoffeeWithHook extends CaffeineBeverageWithHook {
    @Override
    protected void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }

    @Override
    protected void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    public boolean customerWantsCondiments() {
        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        String answer = null;

        System.out.println("Would you like milk and sugar with your coffee (y/n)?");

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}

首先我们实现了 brew() 和 addCondiments() 方法,然后覆盖了 customerWantsCondiments() 方法,用户输入 y 代表用户需要添加奶和糖,n 反之,现在我们执行测试程序。

public class BeverageTestDrive {
    public static void main(String[] args) {
        CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.prepareRecipe();
    }
}

钩子很方便吧,其实我们也可以直接将它设置到超类中。

我们现在又有了一个新的设计原则:「好莱坞原则」。

好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。

允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是「别调用我们,我们会调用你」。

Java 中的模板方法模式

用模板方法排序,数组的 sort(Object[] a) 方法是排序方法我们来看一下它的代码。

public static void sort(Object[] a) {
	Object[] aux = (Object[])a.clone();
	mergeSort(aux, a, 0, a.length, 0);
}

这个方法只是一个辅助方法,用来创建一个数组的拷贝,然后将其传递给 mergeSort() 方法。

private static void mergeSort(Object src[], Object dest[], int low, int high, int off) {
	for (int i=low; i<high; i++) {
		for (int j=i; j>low && ((Comparable)dest[j-1].compareTo((Comparable)dest[j])>0; j--) {
			swap(dest, j, j-1);
		}
	}
	return;
}

这个方法包含排序算法,此算法依赖于 compareTo() 方法实现完成算法。这里就可以想成是一个模板方法,需要实现 compareTo() 方法。现在来看一个例子。比较人的年龄,我们需要一个人的类。

public class People implements Comparable {
    String name;
    int age;

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        People o1 = (People) o;
        return Integer.compare(this.age, o1.age);
    }
}

测试程序。

public class PeopleSortTestDrive {
    public static void main(String[] args) {
        People[] people = {
                new People("Ab", 12),
                new People("zhangsan", 123),
                new People("lisi", 22),
                new People("wangwu", 53),
                new People("zhaoliu", 66)
        };
        System.out.println("Before sorting: ");
        display(people);
        Arrays.sort(people);
        System.out.println("\nAfter sorting:");
        display(people);
    }

    public static void display(People[] people) {
        for (People person : people) {
            System.out.println(person);
        }
    }
}

第一次是无序的打印,第二次是从小到大的顺序。这就说明我们的排序是成功的,关键步骤是我们实现的 Comparable 接口中的 compareTo 方法,我们通过 Integer 类中的 compare(int x, int y) 方法实现比较,所以最终整个数组能完美的排序完成。

写一个 Swing 的窗口程序

下一个例子是 Swing 的 JFrame!JFrame 是最基本的 Swing 容器,继承了一个 paint() 方法,默认状态下 paint() 是不做事情的,因为它是一个钩子,覆盖它,我们就能把自己的代码插入到 JFrame 的算法中。

public class MyFrame extends JFrame {
    public MyFrame(String title) {
        super(title);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.setSize(300, 300);
        this.setVisible(true);
    }

    public void paint(Graphics graphics) {
        super.paint(graphics);
        String msg = "I rule!!";
        graphics.drawString(msg, 100,100);
    }

    public static void main(String[] args) {
        new MyFrame("Head First Design Patterns");
    }
}

我们利用了 paint() 钩子方法,加入了我们自己定义的代码,也是外观模式的应用。

发布了26 篇原创文章 · 获赞 2 · 访问量 2324

猜你喜欢

转载自blog.csdn.net/qq_42909545/article/details/105008722