当前位置: 首页 > news >正文

设计模式之模板方法模式详解(Template Method Pattern)

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。

以下介绍的模板方法模式将解决以上类似的问题。

模板的定义与特点

定义:

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

特点:

优点
  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 它在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点
  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

模板的结构

模板结构的结构图

角色说明

(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

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

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
	抽象方法:在抽象类中申明,由具体子类实现。
	具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
	钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

模板方法实现

下面用模板方法实现制作豆浆的程序

分析:

制作豆浆的流程一般是: 选材—>添加配料—>浸泡—>放到豆浆机打碎。通过添加不同的配料,可以制作出不同口味的豆浆
选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。)

图解

模板方法制作豆浆图解

代码实现

抽象类

//抽象类,表示豆浆
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("第四步:黄豆和配料放到豆浆机去打碎	");
    }

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

具体子类

//具体子类 —— 花生豆浆类
public class RedBeanSoyaMilk extends SoyaMilk{
    @Override
    void addCondiments() {
        System.out.println("加入红豆配料");
    }
}
//具体子类 —— 花生豆浆类
public class PeanutSoyaMilk extends SoyaMilk{
    @Override
    void addCondiments() {
        System.out.println("加上花生配料");
    }
}

客户端

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();
    }
}
/*
	----制作红豆豆浆----
	第一步:选择好的新鲜黄豆	
	加入红豆配料
	第三步, 黄豆和配料开始浸泡, 需要 3 小时 
	第四步:黄豆和配料放到豆浆机去打碎	
	
	---- 制 作 花 生 豆 浆 ----
	第一步:选择好的新鲜黄豆	
	加上花生配料
	第三步, 黄豆和配料开始浸泡, 需要 3 小时 
	第四步:黄豆和配料放到豆浆机去打碎	
*/

模式的应用场景

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

模式扩展

  1. 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
  2. 在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。
  3. 还是用上面做豆浆的例子来讲解,比如,我们希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
//抽象类,表示豆浆
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;
    }
}
//具体子类——纯豆浆类
public class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
    }

    //重写父类的钩子函数,让添加原料方法不再执行
    @Override
    boolean customerWantCondiments() {
        return false;
    }
}

如果钩子方法的代码改变,则程序的运行结果也会改变。

相关文章:

  • 工作苦旅
  • 设计模式之命令模式详解(Command Pattern)
  • 今天试用了VE开发SWT
  • 设计模式之访问者模式详解(Visitor Pattern)
  • GCC for Win32开发环境介绍(2)
  • 设计模式之观察者模式详解(Observer Pattern)
  • 设计模式之迭代器模式详解(Iterator Pattern)
  • asp.net部分优化
  • 设计模式之中介者模式详解(Mediator Pattern)
  • ASP.NET中利用存储过程实现模糊查询
  • 设计模式之备忘录模式详解(Memento Pattern)
  • 从asp转asp.net的相关
  • 设计模式之状态模式详解(State Pattern)
  • 浅谈DataSet 的用法
  • 多维数组和矩阵之顺时针打印二维数组
  • 【Leetcode】101. 对称二叉树
  • [译]前端离线指南(上)
  • Angular 响应式表单 基础例子
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • Javascript基础之Array数组API
  • Java教程_软件开发基础
  • JWT究竟是什么呢?
  • mongo索引构建
  • Redis学习笔记 - pipline(流水线、管道)
  • SQLServer插入数据
  • 理清楚Vue的结构
  • 如何编写一个可升级的智能合约
  • 原生js练习题---第五课
  • Spring第一个helloWorld
  • # include “ “ 和 # include < >两者的区别
  • #define
  • $refs 、$nextTic、动态组件、name的使用
  • %check_box% in rails :coditions={:has_many , :through}
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (二)PySpark3:SparkSQL编程
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (规划)24届春招和25届暑假实习路线准备规划
  • (区间dp) (经典例题) 石子合并
  • (已解决)什么是vue导航守卫
  • (转)shell调试方法
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .net 微服务 服务保护 自动重试 Polly
  • .Net 中Partitioner static与dynamic的性能对比
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .netcore如何运行环境安装到Linux服务器
  • .NET开发者必备的11款免费工具
  • @Bean, @Component, @Configuration简析
  • @ModelAttribute 注解
  • @Service注解让spring找到你的Service bean
  • @Transient注解
  • [Android Studio] 开发Java 程序
  • [AutoSar]BSW_Memory_Stack_003 NVM与APP的显式和隐式同步
  • [C/C++] C/C++中数字与字符串之间的转换
  • [Docker]三.Docker 部署nginx,以及映射端口,挂载数据卷