Java设计模式及应用场景之《模板方法模式》

文章目录

      • 一、模板方法模式定义
      • 二、模板方法模式的结构和说明
      • 三、模板方法模式示例
      • 四、钩子方法
      • 五、模板方法模式的优缺点
      • 六、模板方法模式的应用场景及案例

一、模板方法模式定义

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)

二、模板方法模式的结构和说明

Java设计模式及应用场景之《模板方法模式》_第1张图片

  • AbstractClass: 抽象模板类,用来定义模板方法和基本方法。模板方法内部定义的是一个框架,实现对基本方法的调用,完成固定格式的逻辑操作。基本方法包括抽象方法和普通方法,抽象方法需要延迟到子类中实现。
  • ConcreteClass: 具体实现类,用来实现父类中定义的一个或多个基本方法,完成子类相关的特定功能。

三、模板方法模式示例

假设我们有个制作饮品的功能,我们可以制作茶和咖啡。

制作茶的步骤如下:

  1. 将水烧开
  2. 将茶叶放入杯中
  3. 将烧好的水倒入杯中
  4. 放入柠檬

制作咖啡的步骤如下:

  1. 将水烧开
  2. 将咖啡粉倒入杯中
  3. 将烧好的水倒入杯中
  4. 放入咖啡伴侣

由上可以看到,制作茶和咖啡的步骤基本相同,并且其中第一步和第三部处理过程完全一样,如果逻辑都各自实现的话,是不是就会造成代码重复呢。

我们可以使用模板方法模式来避免这种情况。

首先定义一个抽象模板类,模板类中有一个模板方法,模板方法依次调用烧水、将主材料放入杯中、将烧好的水倒入杯中、放入调味品这四个处理步骤方法。

/**
 * 制作饮品(抽象模板类)
 */
public abstract class Drinks {
     

    /**
     * 模板方法:制作饮品
     * 定义成final,防止子类重写
     */
    public final void makeDrinks(){
     

        // 1、将水烧开
        boilWater();

        // 2、将主材料放入杯中
        drinksIntoCup();

        // 3、将烧好的水倒入杯中
        waterIntoCup();

        // 4、放入调味品
        condimentIntoCup();

    }

    /**
     * 烧水为通用逻辑,没必要在子类中实现
     */
    private void boilWater() {
     
        System.out.println("烧水中...");
    }

    /**
     * 放主材料的抽象方法,具体处理逻辑需要延迟到子类中实现
     */
    protected abstract void drinksIntoCup();

    /**
     * 倒水为通用逻辑,没必要在子类中实现
     */
    private void waterIntoCup() {
     
        System.out.println("将烧好的水倒入杯中");
    }

    /**
     * 放调味品的抽象方法,具体处理逻辑需要延迟到子类中实现
     */
    protected abstract void condimentIntoCup();

}

那么,制作茶和咖啡的子实现类就可以这样来实现:

/**
 * 泡茶
 */
public class Tea extends Drinks{
     

    /**
     * 实现放主材料的处理逻辑
     */
    @Override
    protected void drinksIntoCup() {
     
        System.out.println("将适量茶叶放入杯中");
    }

    /**
     * 实现加调味品的处理逻辑
     */
    @Override
    protected void condimentIntoCup() {
     
        System.out.println("放入适量柠檬片");
    }

}
/**
 * 冲咖啡
 */
public class Coffee extends Drinks{
     

    /**
     * 实现放主材料的处理逻辑
     */
    @Override
    protected void drinksIntoCup() {
     
        System.out.println("将咖啡粉倒入杯中");
    }

    /**
     * 实现加调味品的处理逻辑
     */
    @Override
    protected void condimentIntoCup() {
     
        System.out.println("放入咖啡伴侣");
    }

}

最后,我们来模拟一下,制作茶和制作咖啡的调用方式。

public static void main(String[] args) {
     
	Drinks tea = new Tea();
	tea.makeDrinks();
	
	System.out.println("==================");

	Drinks coffee = new Coffee();
	coffee.makeDrinks();
}

输出如下:

烧水中…
将适量茶叶放入杯中
将烧好的水倒入杯中
放入适量柠檬片
==================
烧水中…
将咖啡粉倒入杯中
将烧好的水倒入杯中
放入咖啡伴侣

四、钩子方法

我们在抽象模板类中,也可以定义一些空实现或者有默认实现的方法,子类中可以选择重写也可以不重写这些方法。这样的方法,我们称为钩子方法。钩子方法为你在实现某一个抽象类的时候提供了可选项,相当于预先提供了一个默认配置。钩子方法的引入使得子类可以控制父类的行为。

举个例子,制作咖啡时,当我们倒入咖啡伴侣后,一般需要搅拌一下,而制作茶的过程中就不需要这个环节。

我们在抽象模板类中加入搅拌的方法。

/**
 * 制作饮品(抽象模板类)
 */
public abstract class Drinks {
     

    /**
     * 模板方法:制作饮品
     * 定义成final,防止子类重写
     */
    public final void makeDrinks(){
     

        // 1、将水烧开
        boilWater();

        // 2、将主材料放入杯中
        drinksIntoCup();

        // 3、将烧好的水倒入杯中
        waterIntoCup();

        // 4、放入调味品
        condimentIntoCup();

        // 5、搅拌均匀
        stir();

    }

    /**
     * 烧水为通用逻辑,没必要在子类中实现
     */
    private void boilWater() {
     
        System.out.println("烧水中...");
    }

    /**
     * 放主材料的抽象方法,具体处理逻辑需要延迟到子类中实现
     */
    protected abstract void drinksIntoCup();

    /**
     * 倒水为通用逻辑,没必要在子类中实现
     */
    private void waterIntoCup() {
     
        System.out.println("将烧好的水倒入杯中");
    }

    /**
     * 放调味品的抽象方法,具体处理逻辑需要延迟到子类中实现
     */
    protected abstract void condimentIntoCup();

    /**
     * 搅拌均匀。
     * 并不是所有饮品都需要搅拌,所以这里给一个空实现。
     * 这个是个钩子方法,意思是,子类中谁想用这个方法,谁就重写一下
     */
    protected void stir(){
     }

}

咖啡需要搅拌,所以我们在咖啡类中,重写实现一下这个搅拌方法。

/**
 * 冲咖啡
 */
public class Coffee extends Drinks{
     

    /**
     * 实现放主材料的处理逻辑
     */
    @Override
    protected void drinksIntoCup() {
     
        System.out.println("将咖啡粉倒入杯中");
    }

    /**
     * 实现加调味品的处理逻辑
     */
    @Override
    protected void condimentIntoCup() {
     
        System.out.println("放入咖啡伴侣");
    }

    /**
     * 重写搅拌方法
     */
    @Override
    protected void stir() {
     
        System.out.println("将咖啡和咖啡伴侣搅拌均匀");
    }
}

重新执行我们的测试方法后,得到下边的输出:

烧水中…
将适量茶叶放入杯中
将烧好的水倒入杯中
放入适量柠檬片
==================
烧水中…
将咖啡粉倒入杯中
将烧好的水倒入杯中
放入咖啡伴侣
将咖啡和咖啡伴侣搅拌均匀

五、模板方法模式的优缺点

优点:

  • 实现代码复用。

缺点:

  • 算法骨架不容易升级。 模板类跟子类非常耦合,如果要对模板方法进行变更,可能就会要求所有子类进行相应的变化。所以,在抽取算法骨架的时候需要特别小心
    ,尽量确保抽取的部分是不会变化的。

六、模板方法模式的应用场景及案例

  • 各个子类中有公共行为,应该抽取出来,从而避免代码重复。
  • 需要固定算法骨架,实现一个算法的不可变部分,并把可变部分留给子类来实现。
  • AQS(AbstractQueuedSynchronizer) 就是模板方法模式的一个典型应用案例。

你可能感兴趣的:(Java设计模式,模板方法模式,钩子方法,模板方法模式应用场景,设计模式,Java模板方法模式)