模板模式

115 阅读5分钟

1 模板模式的原理

模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:

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.

翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

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

2 一个Demo

2.1 需求:豆浆制作问题

编写制作豆浆的程序,说明如下:

  1. 制作豆浆的流程选材->添加配料 ->浸泡->放到豆浆机打碎
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  4. 请使用模板方法模式完成

2.2 代码实现

package com.evan.templateMethod;

/**
 * @Description 抽象类,表示豆浆
 * @ClassName SoyaMilk
 * @Author Evan
 * @date 2019.12.09 22:39
 */
public abstract class SoyaMilk {

    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {

        select();
        addCondiments();
        soak();
        beat();

    }

    //选材料
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆  ");
    }

    //添加不同的配料, 抽象方法, 子类具体实现
    abstract void addCondiments();

    //浸泡
    void soak() {
        System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
    }

    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
    }
}

make() 等 函数定义为 final,避免子类重写它。addCondiments() 定义为 protected abstract,强迫子类去实现。

package com.evan.templateMethod;

import com.evan.decorator.Soy;

/**
 * @Description
 * @ClassName PeanutSoyaMilk
 * @Author Evan
 * @date 2019.12.09 22:47
 */
public class PeanutSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入花生");
    }
}
package com.evan.templateMethod;

/**
 * @Description
 * @ClassName RedBeanSoyaMilk
 * @Author Evan
 * @date 2019.12.09 22:46
 */
public class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入红豆");
    }
}
package com.evan.templateMethod;

/**
 * @Description
 * @ClassName Client
 * @Author Evan
 * @date 2019.12.09 22:47
 */
public class Client {
    public static void main(String[] args) {

        //制作红豆豆浆
        System.out.println("----制作红豆豆浆----");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();
    }

}

2.3 模板方法模式的钩子方法

  1. 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方 法称为“钩子”,
  2. 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,使用钩子方法对前面的模板方法进行改造
package com.evan.templateMethod.improve;

/**
 * @Description
 * @ClassName SoyaMilk
 * @Author Evan
 * @date 2019.12.09 22:48
 */
public abstract class SoyaMilk {

    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {

        select();
        if(customerWantCondiments()){
            addCondiments();
        }
        soak();
        beat();

    }

    //选材料
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆  ");
    }

    //添加不同的配料, 抽象方法, 子类具体实现
    abstract void addCondiments();

    //浸泡
    void soak() {
        System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
    }

    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
    }

    //钩子方法,决定是否需要添加配料
    boolean customerWantCondiments() {
        return true;
    }
}
package com.evan.templateMethod.improve;

/**
 * @Description
 * @ClassName puraSoyaMilk
 * @Author Evan
 * @date 2019.12.09 22:51
 */
public class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
       // 什么都不做
    }

    @Override
    boolean customerWantCondiments() {
        return false;
    }
}
package com.evan.templateMethod.improve;

/**
 * @Description
 * @ClassName Client
 * @Author Evan
 * @date 2019.12.09 22:51
 */
public class Client {

    public static void main(String[] args) {

        //制作红豆豆浆
        System.out.println("----制作红豆豆浆----");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();

        System.out.println("----制作纯豆浆----");
        SoyaMilk pureSoyaMilk = new PureSoyaMilk();
        pureSoyaMilk.make();
    }

}

3 模板方法模式的总结

3.1 模板方法模式的意图

意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

使用场景:

  1. 有多个子类共有的方法,且逻辑相同。
  2. 重要的、复杂的方法,可以考虑作为模板方法。

3.2 模板方法模式的细节

  1. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  4. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  5. 一般模板方法都加上final关键字, 防止子类重写模板方法
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别涉骤在实现时可能不同,通常考虑用模板方法模式来处

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

3.3 模板方法模式的优缺点

优点:

  1. 封装不变部分,扩展可变部分。

  2. 提取公共代码,便于维护。

  3. 行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。