汉堡中的设计模式——模板方法

艺术来源于生活,有时候灵感真的就在那么一瞬间

汉堡中的设计模式——模板方法

看到上图这个板烧没有,这就是我今晚的晚餐了;走进麦当劳里面,有很多很多的汉堡

板烧鸡腿堡、麦辣鸡腿堡、麦香堡、深海鳕鱼堡….

这些个汉堡的制作方式,似乎有着一种很明显的相似感,汉堡的制作方式非常像之前学习过的一种设计模式,今天我们就来聊一聊

我们不妨更加仔细的思考一下上面的问题

  • 我要做一个【板烧鸡腿堡】
  • 取出两块面包,烤热
  • 放上烤热的腌制好的鸡腿肉,撒上独特的酱汁
  • 撒上蔬菜
  • 组合成汉堡
  • 装在纸袋里面
  • 我要做一个【麦辣鸡腿堡】
  • 取出两块面包,烤热
  • 放上炸制好的鸡腿肉,撒上独特的酱汁
  • 撒上蔬菜
  • 组合成汉堡
  • 装在纸袋里面
  • 我要做一个【新奥尔良烤鸡腿堡】
  • 取出两块面包,烤热
  • 放上新奥尔良鸡腿肉,撒上独特的酱汁
  • 撒上蔬菜
  • 组合成汉堡
  • 装在纸袋里面
  • 制作其他汉堡……

有图有真相

汉堡中的设计模式——模板方法

可以发现,除了 步骤2存在差异之外,其他步骤其实都是一样的,那么我们是否可以将 步骤2给抽取出来,其他步骤都不变

就好像 模板一般

那么我们是否可以这么做,改造一下模型图, 将不变的写在超类里面,变化的由子类决定

这就是模板设计模式的思想,下面是《Head First 设计模式》对模板设计模式的解释

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

我们现在就使用模板设计模式来改造一下我们的模型图

改造后的模型图

汉堡中的设计模式——模板方法

从上图就很清晰的看到, 模板设计的理念就是定义一个骨架,把公用部分挪到超类中,同时又把会变化的步骤交给子类来决定

世间众多的设计模式,都要做到一件事,那就是 封装变化点

何谓封装变化点?

  • 模板设计模式:把【个性化操作(这就是变化点)】操作延迟到未来实现,最后插入到整个定义好步骤的算法中
  • 类似的还有策略模式,同样是封装算法家族
  • 还有桥接模式,实例被定义成抽象的,通过接口交互,允许实例自由的变化而不会影响到另外的实例
  • 还有很多很多……

既然模型图出来了,那么就按照模型图来把代码敲一敲吧, 天上飞的理念还是得有落地的那一刻,这是很重要的,实践出真知

/**
 * 定义制作汉堡包的模板流程
 * @author Amg
 * @date 2021/9/24
 */
public abstract class AbstractMakeHamburgerHandler {

    //定义层final,确保子类不会把我"骨架"都修改了
    public final void execute() {
        baking();
        customize();
        vegetable();
        compose();
        finish();
        hook();
    }

    private void baking() {
        System.out.println("取出两块面包,烤热");
    }

    /**
     * 自定义步骤,可以添加你想添加的食材
     */
    protected abstract void customize();

    private void compose() {
        System.out.println("组合汉堡");
    }

    private void vegetable() {
        System.out.println("撒上蔬菜");
    }

    private void packing() {
        System.out.println("用纸袋包裹着");
    }

    private void finish() {
        System.out.println("完成制作!请慢用");
    }

    protected void hook() {
        //这个hook方法就是一个程序"钩子",待会会解释
    }
}

/**
 * 板烧鸡腿堡
 * @author Amg
 * @date 2021/9/24
 */
public class banshao extends AbstractMakeHamburgerHandler {

    @Override
    protected void customize() {
        System.out.println("[制作板烧鸡腿堡] 专属的腌制好的鸡腿肉,撒上独特的酱汁");
    }

}

/**
 * 麦辣鸡腿堡
 *
 * @author Amg
 * @date 2021/9/24
 */
public class mailajituibao extends AbstractMakeHamburgerHandler {

    @Override
    protected void customize() {
        System.out.println("[制作麦辣鸡腿堡] 放上炸制好的鸡腿肉,撒上独特的酱汁");
    }
}

//新奥尔良就不实现了,有兴趣的朋友可以自行敲一敲(主要是肯德基你跑错地方了,这里是麦当劳专场)
public class Client {

    public static void main(String[] args) {
        AbstractMakeHamburgerHandler banshao = new banshao();
        banshao.execute();

        System.out.println("---------------------------------");

        mailajituibao mailajituibao = new mailajituibao();
        mailajituibao.execute();
    }

}

输出结果

汉堡中的设计模式——模板方法

现在我们模型图画了,代码也敲了,那么 模板设计模式有什么优缺点吗?

优点

  • 抽取共用部分到超类中,让程序更加的简洁
  • 封装不变的对象,扩展变化的部分(符合开闭原则)

缺点

  • 采用继承结构,理论上来说,每扩展一个汉堡(推出新品)就需要创建一个新的类继承超类,如果有100个汉堡,就得有100个类,这就会显示很膨胀( 好处是Java 8引入了消费者接口,我们可以使用这个接口,而不必创建一大堆的新类

简单讲解一下Consumer(消费者)接口

先看看这个接口里面的方法

 /**
* Performs this operation on the given argument.

*
* @param t the input argument
*/
void accept(T t);

/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation.  If performing this operation throws an exception,
* the {@code after} operation will not be performed.

*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer andThen(Consumer after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

我们得知道消费者这个角色的第一任务就是 消费嘛,我不管什么乱七八糟的, 我只知道调用accept方法,我就要去消费

至于andThen方法,返回一个组合的Consumer ,它依次执行此操作和after操作。 如果执行任一操作引发异常,则将其转发给组合操作的调用者。 如果执行此操作抛出异常,则不会执行after操作(这段是直接翻译文档过来的,解释得很透彻了)

Java8 Stream中大量使用到消费者和生产者,有兴趣的朋友也可以去了解一下(话说jdk都出到17了…)

所有,如果以Consumer方式使用模板模式,是怎么样的?直接上代码

/**
 * jdk1.8之后做汉堡
 * @author Amg
 * @date 2021/9/24
 */
public class MakeHamburgerHandler {

    /**
     * 这个方法进行复用
     * @param consumer  jdk1.8之后出现的【消费者】接口,接收参数,无返回值,调用accept方法就把信息给输出出来
     */
    private void execute(Consumer consumer) {
        baking();
        consumer.accept(null);
        vegetable();
        compose();
        packing();
        finish();
    }

    private void baking() {
        System.out.println("取出两块面包,烤热");
    }

    private void compose() {
        System.out.println("组合汉堡");
    }

    private void vegetable() {
        System.out.println("撒上蔬菜");
    }

    private void packing() {
        System.out.println("用纸袋包裹着");
    }

    private void finish() {
        System.out.println("制作完成,客官请慢用!");
    }

    /**
     * 板烧鸡腿堡
     */
    public void banshao() {
        execute(a -> System.out.println("[制作板烧鸡腿堡] 专属的腌制好的鸡腿肉,撒上独特的酱汁"));
    }
    /**
    * 麦辣鸡腿堡
    */
    public void mailajituibao() {
        execute(a -> System.out.println("[制作麦辣鸡腿堡] 放上炸制好的鸡腿肉,撒上独特的酱汁"));
    }
}
/**
 * 使用JDK1.8之后提供的supplier、consumer接口,可以不再不要抽象类,以及减少类对象的创建
 * 而且把相同的业务给放在同一个类里面,方便后续的维护;
 *
 * 但是思考一下,这样子就会违反OCP原则,上架和下架汉堡、修改汉堡佐料等等操作都需要去修改{@link MakeHamburgerHandler}
 *
 * @author Amg
 * @date 2021/9/24
 */
public class Client {

    public static void main(String[] args) {

        MakeHamburgerHandler makeHamburgerHandler = new MakeHamburgerHandler();

        makeHamburgerHandler.banshao();

        System.out.println("------------------jdk1.8之后模板模式的使用------------------------");

        makeHamburgerHandler.mailajituibao();

        System.out.println("------------------jdk1.8之后模板模式的使用------------------------");

    }
}

汉堡中的设计模式——模板方法

代码上的注释也很清晰了,这里再总结一下,使用Consumer接口,我们可以不再需要抽象类、以及一大堆的派生子类,想要什么汉堡就包装一下execute方法即可,可以说是非常的好使

但是随之而来的问题也是,我们每次修改都会动到这个类,所以是违反了OCP原则的

最最最最后一个问题,就是上面遗留的hook方法有什么用?

上面遗留的hook方法其实是一个 程序钩子他一般是空方法体或者默认实现,作用是让子类有能力对算法的不同点进行挂钩,意思就是执行或者不执行额外的逻辑

通俗点就是 麦当劳在点完餐之后通常还会有一些推荐点餐,我爱吃薯条(就是你了),你可以选择要还是不要,如果我想在板烧套餐里面添加,麦辣鸡腿堡套餐里面不要,要怎么做呢?

修改我们的代码

public final void execute() {
    baking();
    customize();
    vegetable();
    compose();
    finish();
    //将hook方法更名为isReceiveChips
    if (isReceiveChips()) {
        System.out.println("薯条添加完毕!");
    }
}

/**
* 是否接收推荐过来的薯条,默认不要
* @return false
*/
protected boolean isReceiveChips() {
    return false;
}

//获取用户输入
protected String getUserInput() {

    String answer;

    System.out.println("是否还需要来一份薯条呢?[y/n]");

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    try {
        answer = in.readLine();
    } catch (IOException e) {
        answer = "n";
    }

    return answer;
}

//板烧子类覆盖这个方法
@Override
protected boolean isReceiveChips() {
    String answer = getUserInput();

    if (answer.equalsIgnoreCase("y"))
        return true;
    return false;
}

输出的结果

汉堡中的设计模式——模板方法

可以看到,我们在 板烧套餐里面重写了isReceiveChips方法,并且返回true;而在麦辣鸡腿堡套餐里面就没有这个操作,所以麦辣鸡腿堡套餐采用默认行为

再回顾一下, 钩子的存在,可以让子类有能力对算法的不同点进行挂钩

写完,收工,继续吃汉堡去!

Original: https://www.cnblogs.com/iamamg97/p/15331852.html
Author: 码农Amg
Title: 汉堡中的设计模式——模板方法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/570337/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球