瑞_23种设计模式_模板方法模式

news2024/10/7 12:21:02

文章目录

    • 1 模板方法模式(Template Pattern) ★ 钩子函数
      • 1.1 介绍
      • 1.2 概述
      • 1.3 模板方法模式的结构
      • 1.4 模板方法模式的优缺点
      • 1.5 模板方法模式的使用场景
    • 2 案例一
      • 2.1 需求
      • 2.2 代码实现
    • 3 案例二
      • 3.1 需求
      • 3.2 代码实现
    • 4 JDK源码解析(InputStream类)

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的模板方法模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《瑞_23种设计模式_装饰者模式》
  桥接模式:《瑞_23种设计模式_桥接模式》
  外观模式:《瑞_23种设计模式_外观模式》
  组合模式:《瑞_23种设计模式_组合模式》
  享元模式:《瑞_23种设计模式_享元模式》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《后续更新》
  策略模式:《后续更新》
  命令模式:《后续更新》
 职责链模式:《后续更新》
  状态模式:《后续更新》
 观察者模式:《后续更新》
 中介者模式:《后续更新》
 迭代器模式:《后续更新》
 访问者模式:《后续更新》
 备忘录模式:《后续更新》
 解释器模式:《后续更新》

在这里插入图片描述

1 模板方法模式(Template Pattern) ★ 钩子函数

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

  在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

  瑞:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
  瑞:行为型模式分为类行为模式对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性

模板方法模式属于:类行为型模式

1.1 介绍

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

  • 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

  • 何时使用:有一些通用的方法。

  • 如何解决:将这些通用算法抽象出来。

  • 关键代码:在抽象类实现,其他步骤在子类实现。

  • 应用实例
      1️⃣ 在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
      2️⃣ 西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
      3️⃣ spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

  • 优点
      1️⃣ 封装不变部分,扩展可变部分。
      2️⃣ 提取公共代码,便于维护。
      3️⃣ 行为由父类控制,子类实现。

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

  • 使用场景
      1️⃣ 有多个子类共有的方法,且逻辑相同。
      2️⃣ 重要的、复杂的方法,可以考虑作为模板方法。

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

1.2 概述

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

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

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

1.3 模板方法模式的结构

  • 模板方法(Template Method)模式包含以下主要角色:
      1️⃣ 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
        1️⃣➖1️⃣ 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
        1️⃣➖2️⃣ 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
          1️⃣➖2️⃣➖1️⃣ 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
          1️⃣➖2️⃣➖2️⃣ 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
          1️⃣➖2️⃣➖3️⃣ 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
      2️⃣ 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

1.4 模板方法模式的优缺点

优点:

  • 提高代码复用性
    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制
    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

1.5 模板方法模式的使用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。



2 案例一

【案例】炒菜

2.1 需求

  正文模版炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

在这里插入图片描述

2.2 代码实现

定义模板方法和基本方法(抽象类)
/**
 * 抽象类(定义模板方法和基本方法)
 *
 * @author LiaoYuXing-Ray
 **/
public abstract class AbstractClass {

    // 模板方法定义 为防止恶意操作,一般模板方法都加上 final 关键词
    public final void cookProcess() {
        pourOil();
        heatOil();
        pourVegetable();
        pourSauce();
        fry();
    }

    // 第一步:倒油是一样的,所以直接实现
    public void pourOil() {
        System.out.println("倒油");
    }

    // 第二步:热油是一样的,所以直接实现
    public void heatOil() {
        System.out.println("热油");
    }

    // 第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
    public abstract void pourVegetable();

    // 第四步:倒调味料是不一样
    public abstract void pourSauce();


    // 第五步:翻炒是一样的,所以直接实现
    public void fry(){
        System.out.println("炒啊炒啊炒到熟啊");
    }
}

炒包菜类(类)
/**
 * 炒包菜类
 *
 * @author LiaoYuXing-Ray
 **/
public class ConcreteClass_BaoCai extends AbstractClass {

    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是包菜");
    }
    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是辣椒");
    }
}

炒菜心类(类)
/**
 * 炒菜心类
 *
 * @author LiaoYuXing-Ray
 **/
public class ConcreteClass_CaiXin extends AbstractClass {

    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是蒜蓉");
    }
}

测试类
/**
 * 测试类
 *
 * @author LiaoYuXing-Ray
 **/
public class Client {
    public static void main(String[] args) {
        // 炒手撕包菜
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        // 调用炒菜的功能
        baoCai.cookProcess();

        System.out.println("\n=== 我是一条华丽的分割线 ===\n");

        // 炒蒜蓉菜心
        ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
        // 调用炒菜的功能
        caiXin.cookProcess();
    }
}

  代码运行结果如下:

	倒油
	热油
	下锅的蔬菜是包菜
	下锅的酱料是辣椒
	炒啊炒啊炒到熟啊
	
	=== 我是一条华丽的分割线 ===
	
	倒油
	热油
	下锅的蔬菜是菜心
	下锅的酱料是蒜蓉
	炒啊炒啊炒到熟啊

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




3 案例二

本案例为菜鸟教程中的案例

3.1 需求

  我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。

  TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

在这里插入图片描述

3.2 代码实现

步骤1

  创建一个抽象类,它的模板方法被设置为 final。

Game.java
public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   // 模板
   public final void play(){
 
      // 初始化游戏
      initialize();
 
      // 开始游戏
      startPlay();
 
      // 结束游戏
      endPlay();
   }
}

步骤2

  创建扩展了上述类的实体类。

Cricket.java
public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
Football.java
public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}

步骤3

  使用 Game 的模板方法 play() 来演示游戏的定义方式。

TemplatePatternDemo.java
public class TemplatePatternDemo {
   public static void main(String[] args) {
 
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();      
   }
}

步骤4

  执行程序,输出结果:

	Cricket Game Initialized! Start playing.
	Cricket Game Started. Enjoy the game!
	Cricket Game Finished!
	
	Football Game Initialized! Start playing.
	Football Game Started. Enjoy the game!
	Football Game Finished!

4 JDK源码解析(InputStream类)

  InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

public abstract class InputStream implements Closeable {
    // 抽象方法,要求子类必须重写
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read(); // 调用了无参的read方法,该方法是每次读取一个字节数据
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}

  从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。

  在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取 len 个字节数据。具体如何读取一个字节数据,是由子类实现。




本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1501925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Javaweb day13 day14 day15

spring boot 快速入门 写法 http协议 请求协议 响应协议 协议解析 Tomcat

功能强大使用简单的截图/贴图工具,PixPin

一、下载链接 PixPin 截图/贴图/长截图/文字识别/标注 | PixPin 截图/贴图/长截图/文字识别/标注 (pixpinapp.com) 二、功能 截图/贴图/长截图/文字识别/标注 三、安装教程 根据提示安装即可&#xff1a; 四、快捷键 1.软件自带快捷键&#xff08;右击PixPin查看 &#xff09…

Tomcat的安装

下载Tomcat&#xff08;这里以Tomcat8.5为例&#xff09; 直接进入官网进行下载&#xff0c;Tomcat官网 选择需要下载的版本&#xff0c;点击下载这里一定要注意&#xff1a;下载路径一定要记住&#xff0c;并且路径中尽量不要有中文&#xff01;&#xff01;&#xff01;&…

C/C++编程-理论学习-通信协议理论

通信协议理论 protobuf简述使用简介proto 文件为了nanopb 编译.proto文件修改生成器行为 streamsoutput streamsinput streams Data types(数据类型)Field callbacks(字段回调)Encoding callbacks(编码回调)Message descriptor(信息描述)三个关键字required、optional、repeate…

【编程实践】matlab中的转义字符

简记 这个报错是因为在sprintf函数中使用了无效的转义字符\U。在MATLAB中&#xff0c;转义字符\U是无效的&#xff0c;因此会导致警告。 检查sprintf函数中的格式化字符串是否包含了无效的转义字符。确保只使用MATLAB支持的转义字符。 如果想要输出一个反斜杠字符\&#xff0c…

Graphpad Prism10.2.1(395) 安装教程 (含Win/Mac版)

GraphPad Prism GraphPad Prism是一款非常专业强大的科研医学生物数据处理绘图软件&#xff0c;它可以将科学图形、综合曲线拟合&#xff08;非线性回归&#xff09;、可理解的统计数据、数据组织结合在一起&#xff0c;除了最基本的数据统计分析外&#xff0c;还能自动生成统…

ES分布式搜索-IK分词器

ES分词器-IK 1、为什么使用分词器&#xff1f; es在创建倒排索引时需要对文档分词&#xff1b;在搜索时&#xff0c;需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。 我们在kibana的DevTools中测试&#xff1a; GET /_analyze {"analyzer": "…

java数据结构与算法刷题-----LeetCode208. 实现 Trie (前缀树)

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路 就是一种数据结构&#xff0c;一般自动补完&#xff0c…

window vscode安装node.js

window vscode安装node.js 官网下好vscode 和nodejs 选.msi的安装 点这个安装 下载完 继续安装 完毕后倒杯水喝个茶等2分钟 重启VScode 或者在cmd 运行 npm -v node -v 显示版本号则成功

Swift SwiftUI 学习笔记 2024

Swift SwiftUI 学习笔记 2024 一、资源 视频资源 StanfordUnivercity 公开课 2023: https://cs193p.sites.stanford.edu/2023 教程 Swift 初识&#xff1a;基础语法&#xff1a;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/…

CACLP预告 | 飞凌嵌入式与您相约山城重庆

第二十一届中国国际检验医学暨输血仪器试剂博览会&#xff08;CACLP&#xff09;将于2024年3月16日-18日在重庆国际博览中心举行。本次会议将探讨科技创新趋势&#xff0c;展示最新成果&#xff0c;发现和挖掘颠覆性技术和创新产品&#xff0c;引领实验医学体外诊断科技创新和未…

【HarmonyOS】ArkTS-对象

目录 对象对象的定义对象的使用实例 对象 作用&#xff1a;用于描述一个物体的特征和行为。 对象&#xff1a;是一个可以存储多个数据的容器。 对象的定义 let 对象名称: 对象结构类型 值通过interface 接口约定 对象结构类型 interface 接口名 { 属性1: 类型1 属性2: 类型2…

数字音频工作站(DAW)fl studio 21 for mac 21.2.3.3586中文版图文安装教程

随着音乐制作行业的不断发展&#xff0c;越来越多的音乐人和制作人开始使用数字音频工作站&#xff08;DAW&#xff09;来创作和制作音乐。其中FL Studio 21是一个备受欢迎的选择&#xff0c;因为它提供了强大的音乐制作工具和易于使用的界面。 然而&#xff0c;一直以来&…

在IDEA使用HBase Java API连接

一、下载安装Maven并加载到IDEA中 官网地址:Maven – Download Apache Maven 将对应版本的压缩包下载到本地,并新建一个文件夹Localwarehouse&#xff0c;用来保存下载的依赖文件 配置maven的系统环境配置&#xff0c;将maven安装的bin目录地址写入path环境变量&#xff1a; …

面向对象中接口(亦称: 协议、protocol、 “鸭子类型”)与抽象类

接口与类相比 由编译器强制的一个模块间协作的合约(Contract)&#xff1a; 接口是一个由编译器强制的模块间协作的合约。它定义了一组方法的契约&#xff0c;所有实现该接口的类都必须提供这些方法的具体实现。这种强制性保证了不同模块之间的协作方式的一致性和可靠性。举例来…

荔枝派zero驱动开发06:GPIO操作(platform框架)

参考&#xff1a; 正点原子Linux第五十四章 platform设备驱动实验 一张图掌握 Linux platform 平台设备驱动框架 上一篇&#xff1a;荔枝派zero驱动开发05&#xff1a;GPIO操作&#xff08;使用GPIO子系统&#xff09; 下一篇&#xff1a;更新中… 概述 platform是一种分层思…

在Leaflet中使用Turf.js生成范围多边形的两种实现方式

目录 前言 一、场景需求 1、Leaflet.js的不足 2、Turf.js 二、原始数据展示 1、点位数据展示 2、定义样式 3、定位数据初始化 三、Turfjs中bbox生成 1、官网讲解 2、轨迹bbox生成 四、Turfjs生成外包多边形 1、官网例子 2、凸多边形生成 总结 前言 在一些共享出…

比特币普通地址、隔离见证(兼容)、隔离见证(原生)、Taproot 地址傻傻分不清楚

我们在使用比特币钱包的时候&#xff0c;可以看到各种地址类型&#xff1a;普通地址、隔离见证&#xff08;兼容&#xff09;、隔离见证&#xff08;原生&#xff09;、Taproot 地址。 看得我们一脸懵逼&#xff0c;为什么会有这么多种类型的地址&#xff1f; 它们之间都有什么…

17、电源管理入门之Power supply子系统

目录 1. Power supply框架都做些什么 2. 相关数据结构和接口 2.1 数据结构 2.2 接口 3. 充电驱动 3.1 Charger Manager 3.2 Fuel Gauge 3.3 Charger IC 4. 怎样基于power supply class编写PSY driver 参考资料: 对于便携设备来说,电源管理更加的重要,因为电池电量…

SaulLM-7B: A pioneering Large Language Model for Law

SaulLM-7B: A pioneering Large Language Model for Law 相关链接&#xff1a;arxiv 关键字&#xff1a;Large Language Model、Legal Domain、SaulLM-7B、Instructional Fine-tuning、Legal Corpora 摘要 本文中&#xff0c;我们介绍了SaulLM-7B&#xff0c;这是为法律领域量…