设计模式——模板方法模式

Posted 0x3f3f3f3f

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式——模板方法模式相关的知识,希望对你有一定的参考价值。

模板方法模式定义

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

例子

举一个生活中的场景:泡咖啡和泡茶。
泡咖啡的步骤为:

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

泡茶的步骤为:

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

我们用java类来实现这两个过程,代码如下

//咖啡类
public class Coffee 
    void prepareRecipe() 
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    
    private void addSugarAndMilk() 
        System.out.println("Adding Sugar and Milk");
    
    private void pourInCup() 
        System.out.println("Pouring into cup");
    
    private void brewCoffeeGrinds() 
        System.out.println("Dripping coffee through filter");
    
    private void boilWater() 
        System.out.println("Boiliing water");
    

//茶类
public class Tea 
    void prepareRecipe() 
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    
    private void addLemon() 
        System.out.println("Add Lemon");
    
    private void pourInCup() 
        System.out.println("Pouring into cup");
    
    private void steepTeaBag() 
        System.out.println("Steeping the tea");
    
    private void boilWater() 
        System.out.println("Boiling water");
    

在这两个类中,我们可以看到,$boilWater()$ 方法和 $pourIncup()$ 方法是茶和咖啡类共有的,而剩下的两个方法是茶类和咖啡类专有的。因此我们可以设计一下,把共同的部分抽取出来,放到一个基类中。
而不同的部分虽然不能被抽取出来,但是他们本质上其实是一样的,只是应用到了不同的饮料上。这样,我们就可以从这两个子类中再次抽象一下,把冲泡方法和加调料方法抽象出来。
父类代码如下

public abstract class CaffeineBeverage 
    final void prepareRecipe() 
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    
    abstract void brew();
    abstract void addCondiments();

    void boilWater() 
        System.out.println("Boiling water");
    
    void pourInCup() 
        System.out.println("Pouring into cup");
    

这里咖啡因饮料是一个抽象类。
我们用同一个$prepareRecipe()$方法来处理茶和咖啡,$prepareRecipe()$被声明为$final$,因为我们不希望子类覆盖这个方法。
因为咖啡和茶的处理方法不同,因此我们把这两个方法声明为抽象方法,剩余的东西留给子类去实现。
继承咖啡因饮料类来实现咖啡和茶类

//咖啡类
public class Coffee extends CaffeineBeverage
    @Override
    void brew() 
        System.out.println("Dripping Coffee through filter");
    
    @Override
    void addCondiments() 
        System.out.println("Adding Sugar and Milk");
    

//茶类
public class Tea extends CaffeineBeverage
    @Override
    void brew() 
        System.out.println("Steeping the tea");
    
    @Override
    void addCondiments() 
        System.out.println("Adding Lemon");
    

上述就是一个模板方法模式的经典案例,而其中的$prepareRecipe()$就是一个模板方法。在这个模板中,算法内的每一个步骤都被一个方法代表了,某些方法是由这个类来实现的,而某些方法是由子类实现的,子类实现的方法必须在父类中声明为抽象方法。模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

对模板方法进行挂钩

钩子是一种被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

使用钩子的例子

public abstract class CaffeineBeverageWithHook 
    void prepareRecipe() 
        boilder();
        brew();
        pourInCup();
        if (customerWantsCondiments()) 
            addCondiments();
        
    
    abstract void brew();
    abstract void addCondiments();
    void boilder() 
        System.out.println("Boilding water");
    
    void pourInCup() 
        System.out.println("Pouring into cup");
    
    boolean customerWantsCondiments() 
        return true;
    

在上述抽象类中,我们加上了一个条件语句,而该条件是否成立是由一个具体方法$customerWantsCondiments()$决定的,如果顾客想要加调料,这是我们才调用$addCondiments()$
在父类中,我们定义出这个方法,通常是空的缺省实现。这个方法只会返回$true$,不做别的事。这就是一个钩子,子类可以覆盖这个方法,但不一定要这么做。
而为了使用钩子,我们就需要在子类中覆盖它。具体实现由业务逻辑决定。在这里,钩子控制了饮料中是否加调料,因此,实现逻辑可以是简单的询问顾客的输入做判断即可。
实现

public class CoffeeWithHook extends CaffeineBeverageWithHook
    @Override
    public void brew() 
        System.out.println("Dripping Coffee through filter");
    
    @Override
    public void addCondiments() 
        System.out.println("Adding Sugar and Milk");
    
    @Override
    public boolean customerWantsCondiments() 
        String answer = getUserInput();
        if (answer.toLowerCase().startsWith("y")) 
            return true;
         else 
            return false;
        
    
    private String getUserInput() 
        Scanner sc = new Scanner(System.in);
        System.out.println("Would you like milk and sugar with your coffee (y / n) ?");  
        String answer = sc.nextLine();
        return answer;
    

上述咖啡类就继承了带有钩子的父类,并且重写了钩子方法,对用户的输入做了判断,已达到钩子的效果。
写一个测试类来测试一下

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

执行结果

以上是关于设计模式——模板方法模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之模板方法模式C++实现

4.装饰者模式

设计模式--装饰者模式

9.装饰着模式

Java设计模式之装饰者模式

7 装饰者模式