【行为型模式】模板方法模式

优秀借鉴

1、概述

模板方法模式(Template Method)是一种设计模式,它定义了一个算法的框架,并将具体步骤延迟到子类中实现。在该模式中,父类中定义一个模板方法来描述算法的基本流程,在这个过程中,某些步骤可以通过抽象方法或空置来延迟到子类中实现。

通常情况下,当我们处理一些相似的任务时,会发现这些任务之间有很多共性,只是其中一些步骤不同而已。如果每次都重复编写代码,既费时又容易出错。因此,为了解决这个问题,就产生了模板方法模式,它把这些共性代码抽象到父类中,子类只需要覆盖特定的步骤即可。

2、结构

模板方法模式包含以下几个角色:

  1. 抽象类(Abstract Class):定义抽象方法和模板方法,抽象方法由子类实现,模板方法描述算法的基本流程,并使用抽象方法来延迟到子类中实现:

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法;

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现;

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承;

    • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

  2. 具体类(Concrete Class):继承自抽象类,实现抽象方法。

uml

3、实现方式

3.1、案例引入

可以将模板方法模式类比于制作蛋糕的过程。在制作蛋糕时,有很多共性的步骤,例如准备材料、打好鸡蛋液、混合面粉和糖等,但是每种蛋糕可能会有一些特殊的步骤或要求。如果我们针对每种蛋糕都写一份完整的制作流程,那么代码量会非常大,而且维护起来也不方便。

因此,我们可以使用模板方法模式来解决这个问题。我们可以定义一个抽象的蛋糕制作类,其中包含了一系列基本的制作步骤,例如准备材料、打好鸡蛋液、混合面粉和糖等。然后,我们再定义具体的蛋糕制作类,例如水果蛋糕制作类、巧克力蛋糕制作类等,它们继承自抽象的蛋糕制作类,并实现其中的抽象方法,例如加入水果、加入巧克力等。

当需要制作某种特定的蛋糕时,我们只需要选择相应的具体类即可,无需重复编写制作流程,代码更加简洁易懂,也更易于维护。

案例uml

3.2、结构分析

在上述案例中,模板方法模式的角色分别对应如下:

  1. 抽象类 Cake:是一个抽象的蛋糕制作类,其中包含了一系列基本的制作步骤,并定义了一个模板方法 make() 来封装这些基本步骤:

    • make() 方法:是一个具体方法,它包含了一系列基本的制作步骤,并且定义了整个算法的流程和骨架。它通过调用抽象方法和钩子方法来完成算法中的特殊步骤和可选步骤;
    • addSpecialIngredient() 方法:是一个抽象方法,用于在蛋糕制作中添加特殊食材。由于不同的蛋糕需要添加不同的特殊食材,因此将其定义为抽象方法,由子类实现;
    • needFrosting() 方法:是一个钩子方法,用于判断是否需要覆盖蛋糕。由于不同的蛋糕可能需要或者不需要覆盖,因此将其定义为钩子方法,由子类决定是否进行覆盖;
    • prepareIngredients()mixEggLiquid()mixFlourAndSugar()bake()addFrosting() 方法:都是具体方法,用于实现蛋糕制作中的基本步骤。这些方法在模板方法中被调用,但是它们的具体实现可以被子类所重写;
  2. 具体类 FruitCakeChocolateCake:是 Cake 的具体子类,它们各自实现了抽象方法 addSpecialIngredient()needFrosting() 来完成特殊的步骤,并对需要覆盖的基本步骤进行了重写。

3.3、具体实现

好的,这里是使用中文描述并且添加了方法注释的 Java 代码:

首先,我们定义一个抽象的蛋糕制作类 Cake,其中包含了一系列基本的制作步骤:

public abstract class Cake {

    /**
     * 制作蛋糕的模板方法
     */
    public final void make() {
        // 准备材料
        prepareIngredients(); 
        // 打好鸡蛋液
        mixEggLiquid(); 
        // 混合面粉和糖
        mixFlourAndSugar(); 
        // 加入特殊食材
        addSpecialIngredient(); 
        // 在烤箱中烘烤
        bake(); 

        if (needFrosting()) {
            // 如果需要覆盖,再添加霜糖
            addFrosting(); 
        }
    }

    /**
     * 抽象方法:加入特殊食材
     */
    protected abstract void addSpecialIngredient();

    /**
     * 抽象方法:是否需要覆盖
     */
    protected abstract boolean needFrosting();

    /**
     * 准备材料
     */
    private void prepareIngredients() {
        System.out.println("准备材料...");
    }

    /**
     * 打好鸡蛋液
     */
    private void mixEggLiquid() {
        System.out.println("打好鸡蛋液...");
    }

    /**
     * 混合面粉和糖
     */
    private void mixFlourAndSugar() {
        System.out.println("混合面粉和糖...");
    }

    /**
     * 在烤箱中烘烤
     */
    private void bake() {
        System.out.println("在烤箱中烘烤...");
    }

    /**
     * 添加霜糖
     */
    private void addFrosting() {
        System.out.println("给蛋糕上添加霜糖...");
    }
}

在其中,make() 方法就是模板方法,其中包含了一系列基本的制作步骤。其中涉及到的一些特殊步骤由抽象方法 addSpecialIngredient()needFrosting() 来延迟到子类中实现。

然后,我们定义一个具体的水果蛋糕制作类 FruitCake,它继承自抽象的蛋糕制作类,并实现其中的抽象方法:

public class FruitCake extends Cake {

    /**
     * 加入特殊食材:水果
     */
    @Override
    protected void addSpecialIngredient() {
        System.out.println("将水果加入蛋糕中...");
    }

    /**
     * 是否需要覆盖:不需要
     */
    @Override
    protected boolean needFrosting() {
        return false;
    }
}

在其中,addSpecialIngredient() 方法实现了加入水果这一特殊步骤,needFrosting() 方法则表明这种蛋糕不需要覆盖。

最后,我们定义一个具体的巧克力蛋糕制作类 ChocolateCake,它同样继承自抽象的蛋糕制作类,并实现其中的抽象方法:

public class ChocolateCake extends Cake {

    /**
     * 加入特殊食材:巧克力
     */
    @Override
    protected void addSpecialIngredient() {
        System.out.println("将巧克力加入蛋糕中...");
    }

    /**
     * 是否需要覆盖:需要
     */
    @Override
    protected boolean needFrosting() {
        return true;
    }
}

在其中,addSpecialIngredient() 方法实现了加入巧克力这一特殊步骤,needFrosting() 方法则表明这种蛋糕需要覆盖。

最后,我们可以通过以下代码来测试上述代码:

public class Test {
    public static void main(String[] args) {
        Cake fruitCake = new FruitCake();
        fruitCake.make();

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

        Cake chocolateCake = new ChocolateCake();
        chocolateCake.make();
    }
}

运行结果如下:

准备材料...
打好鸡蛋液...
混合面粉和糖...
将水果加入蛋糕中...
在烤箱中烘烤...
-----------------------
准备材料...
打好鸡蛋液...
混合面粉和糖...
将巧克力加入蛋糕中...
在烤箱中烘烤...
给蛋糕上添加霜糖...

4、模板方法模式优缺点

模板方法模式的优点:

  1. 提高代码复用性:将相同的部分抽象出来成为模板,避免重复的代码,同时提高了代码的复用性。

  2. 便于维护:模板方法模式提供了稳定的结构,使得子类的实现变得简单明了,也更容易进行维护。

  3. 实现了反向控制:通过把不变行为的控制权交给父类,将变化行为留给子类来实现,实现了反向控制,符合“开闭原则”。

模板方法模式的缺点:

  1. 模板方法模式会带来一定的复杂度,增加了代码结构和抽象层次。

  2. 在具体实现繁琐多变的情况下,并不适合使用模板方法模式。

优点缺点
提高代码复用性带来一定的复杂度
便于维护在具体实现繁琐多变的情况下,并不适合使用模板方法模式
实现了反向控制,符合“开闭原则”

5、应用场景

模板方法模式通常用于以下场景:

  1. 父类和子类之间共享一组行为或算法,并且其实现步骤基本相同,只有个别步骤的实现有所不同。

  2. 需要在多个项目或多个版本中重复使用相同的代码逻辑。

  3. 需要在代码运行时动态地指定某些部分的行为,而不是静态地实现在代码中。

  4. 多个程序或系统中存在类似的业务流程,但其中个别步骤的实现有所不同,可以使用模板方法模式将这些不同的实现抽象出来,由具体的子类去实现。

模板方法模式适用于一些希望通过把固定流程的代码封装到基类中的方式来控制子类行为的情况。它可用于框架设计、通用算法设计等场景。

以下是一些Java和Spring中的模板方法模式的例子:

  1. java.util.AbstractList:在Java集合框架中,AbstractList 是一个具体的抽象类,它实现了 List 接口,并提供了大量公共的方法。AbstractList 的子类需要实现的是 get()size() 方法。

  2. java.io.InputStream:用于输入字节流的抽象类,它提供了很多与读取字节流相关的方法,如 read(), skip(), available() 等等。

  3. javax.servlet.http.HttpServlet:是 Servlet 的抽象类,定义了 service() 方法作为 HTTP 请求抽象处理的入口点。开发者继承此类并重写 service() 方法即可实现自己的 Servlet。

  4. org.springframework.jdbc.core.JdbcTemplate:Spring JDBC 模块提供了 JdbcTemplate 类来简化数据库访问。JdbcTemplate 是一个具体的类,由 Spring 框架实现的,它使用模板方法设计模式来执行 SQL 查询、更新、存储过程调用等操作。开发者只需要继承 JdbcTemplate 并实现其抽象方法即可实现自己的数据访问操作。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈宝子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值