模板方法模式-封装一套算法流程

公号:码农充电站pro
主页:https://codeshellme.github.io

今天来介绍模板方法模式Template Method Design Pattern)。

1,制作饮料的过程

在这里插入图片描述

假如我们要制作两种饮料:苹果饮料和橙子饮料。这两种饮料的制作流程如下:

  • 苹果饮料制作流程
    • 把苹果榨成苹果汁
    • 将苹果汁倒入杯中
    • 向杯中倒入水
    • 根据客户喜好是否放入白糖
      • 喜欢则放入白糖,否则不放白糖
  • 橙子饮料制作流程
    • 把橙子榨成橙汁
    • 将橙汁倒入杯中
    • 向杯中倒入水
    • 根据客户喜好是否放入白糖
      • 喜欢则放入白糖,否则不放白糖

2,模拟制作饮料

如果要模拟饮料的制作过程,按照最直接的想法,我们创建两个类 AppleBeverageOrangeBeverage 分别用于制作苹果饮料和橙子饮料。

首先根据苹果饮料的制作流程编写 AppleBeverage 类:

class AppleBeverage {
    private boolean isSweet;

    public AppleBeverage(boolean isSweet) {
        this.isSweet = isSweet;
    }

    // 把苹果榨成苹果汁
    public void squeezeAppleJuice() {
        System.out.println("squeeze apple juice");
    }

    // 将苹果汁倒入杯中
    public void appleJuiceToCup() {
        System.out.println("pour the apple juice into the cup");
    }

    // 向杯中倒入水
    public void waterToCup() {
        System.out.println("pour water into the cup");
    }

    // 向杯中倒入白糖
    public void sugarToCup() {
        System.out.println("pour sugar into the cup");
    }

    // 制作苹果饮料
    public void makeAppleBeverage() {
        squeezeAppleJuice();
        appleJuiceToCup();
        waterToCup();

        if (isSweet) {
            sugarToCup();
        }
    }
}

再根据橙子饮料的制作流程编写 OrangeBeverage 类:

class OrangeBeverage {
    private boolean isSweet;

    public OrangeBeverage(boolean isSweet) {
        this.isSweet = isSweet;
    }

    // 把橙子榨成橙汁
    public void squeezeOrangeJuice() {
        System.out.println("squeeze orange juice");
    }

    // 将橙汁倒入杯中
    public void orangeJuiceToCup() {
        System.out.println("pour the orange juice into the cup");
    }

    // 向杯中倒入水
    public void waterToCup() {
        System.out.println("pour water into the cup");
    }

    // 向杯中倒入白糖
    public void sugarToCup() {
        System.out.println("pour sugar into the cup");
    }

    // 制作橙子饮料
    public void makeOrangeBeverage() {
        squeezeOrangeJuice();
        orangeJuiceToCup();
        waterToCup();

        if (isSweet) {
            sugarToCup();
        }
    }
}

3,分析代码

可以看到上面两个类的代码非常简单,为了更加详细的分析,我画出了这两个类的类图:

在这里插入图片描述

我将这两个类中的方法相同的部分用蓝色标了出来,可以看到,这两个类中的 waterToCupsugarToCup 方法一模一样,其它三个方法也是非常的相似。

这样的代码显然是没有复用已有的代码。

4,改进代码

那么,自然而然,我们可以将两个类中相同的部分,抽象出来放入一个父类中,然后不同的部分让子类去实现。

因此,我们可以编写出父类,如下:

abstract class Beverage {
    protected boolean isSweet;

    // 榨果汁
    public abstract void squeezeJuice();
    
    // 将果汁倒入杯中
    public abstract void juiceToCup();
    
    // 向杯中倒入水
    public void waterToCup() {
        System.out.println("pour water into the cup");
    }

    // 向杯中倒入白糖
    public void sugarToCup() {
        System.out.println("pour sugar into the cup");
    }

    // 制作苹果饮料
    public final void makeBeverage() {
        squeezeJuice();
        juiceToCup();
        waterToCup();
        
        // 根据喜好是否加白糖
        if (isSweet) {
            sugarToCup();
        }
    }
}

我们将所有相同的代码都抽取到了 Beverage 类中,相同的部分有:

  • isSweet 变量
  • waterToCup 方法
  • sugarToCup 方法
  • makeBeverage 方法

其中 makeBeverage 方法使用了 final 关键字来修饰,表示我们不希望子类去修改它。

不同的部分有:

  • squeezeJuice 方法
  • juiceToCup 方法

这两个方法都是抽象方法,表示我们希望子类根据自己的需求去实现。最终在子类中调用 makeBeverage 方法时,makeBeverage 会依据多态性来调用正确的 squeezeJuicejuiceToCup 方法。

下面编写 AppleBeverage 类:

class AppleBeverage extends Beverage {
    public AppleBeverage(boolean isSweet) {
        this.isSweet = isSweet;
    }

    public void squeezeJuice() {
        System.out.println("squeeze apple juice");
    }

    public void juiceToCup() {
        System.out.println("pour the apple juice into the cup");
    }
}

AppleBeverage 继承了 Beverage,并且实现了 squeezeJuicejuiceToCup 方法。

再编写 OrangeBeverage 类:

class OrangeBeverage extends Beverage {
    public OrangeBeverage(boolean isSweet) {
        this.isSweet = isSweet;
    }

    public void squeezeJuice() {
        System.out.println("squeeze orange juice");
    }

    public void juiceToCup() {
        System.out.println("pour the orange juice into the cup");
    }
}

OrangeBeverage 继承了 Beverage,并且实现了 squeezeJuicejuiceToCup 方法。

经过改进后的代码类图如下:

在这里插入图片描述

可以看到经过改进的代码,重复的代码都抽取到了父类中,能复用的代码都进行了复用,子类只需根据自己的需要实现父类的抽象方法就行。

我将所有代码放在了这里,供大家参考。

5,模板方法

实际上,上面代码的实现方式就使用到了模板方法模式

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

这里的算法指的是实际项目中的业务逻辑

模板方法的类图很简单,如下:

在这里插入图片描述

模板方法模式的重点在于,它在父类中定义了一个通用的算法框架templateMethod),也就是上面代码中的 makeBeverage 方法,这个方法就是模板方法,一般用 final 修饰,用于防止子类覆盖。

另外还有一些抽象方法,这些抽象方法都用 abstract 进行了修饰,表示必须由子类实现。算法框架调用了这些抽象方法,这样就相当于子类重新定义了算法中的某些步骤。

在上面代码的 makeBeverage 方法中还用到了一个变量 isSweet,这个变量在子类对象中的不同取值,会影响到 makeBeverage 的执行流程。

这个 isSweet 变量叫作“钩子”,钩子可以是一个变量,也可以是一个方法,它可以改变模板方法的执行流程。

6,总结

模板方法模式提供了一个算法步骤,从中我们能看到代码复用的技巧。

模板方法模式中的抽象方法由子类实现,这意味着父类定义了算法的框架流程,而将算法的实现延迟到了子类中。

我们通常会将模板方法工厂方法放在一起比较,这两个模式有一个明显的不同点,就是模板方法模式将一个算法流程中的某些步骤的具体实现延迟到了子类中,而工厂方法模式是将对象的创建延迟到了子类中。

Java JDK 中,我们能看到很多模板方法的应用案例,比如 InputStream.read(byte b[],int off,int len) 方法。

(本节完。)

推荐阅读:

观察者模式-将消息通知给观察者

装饰者模式-动态的包装原有对象的行为

命令模式-将请求封装成对象

适配器模式-让不兼容的接口得以适配

外观模式-简化子系统的复杂性

欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结