Java 设计模式——模板方法模式

news2024/12/24 8:35:53

目录

  • 1.概述
  • 2.结构
  • 3.案例实现
    • 3.1.抽象类
    • 3.2.具体子类
    • 3.3.测试
  • 4.优缺点
  • 5.使用场景
  • 6.JDK 源码解析
    • 6.1.InputStream
    • 6.2.AbstractQueuedSynchronizer

1.概述

(1)在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。例如,去银行办理业务一般要经过以下 4 个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

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

2.结构

模板方法 (Template Method) 模式包含以下主要角色:

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

3.案例实现

【例】炒菜。炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
在这里插入图片描述
具体实现代码如下:

3.1.抽象类

//抽象类,定义模板方法和基本方法
public abstract class AbstractClass {
    
    //模板方法: 算法的骨架,按某种顺序调用其包含的基本方法
    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("炒啊炒啊炒到熟啊");
    }
}

3.2.具体子类

ConcreteClass_BaoCai.java

//炒包菜类
public class ConcreteClass_BaoCai extends AbstractClass {
    
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是包菜");
    }
    
    public void pourSauce() {
        System.out.println("下锅的酱料是辣椒");
    }
}

ConcreteClass_CaiXin.java

//炒菜心类
public class ConcreteClass_CaiXin extends AbstractClass {
    
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是菜心");
    }
    
    public void pourSauce() {
        System.out.println("下锅的酱料是蒜蓉");
    }
}

3.3.测试

Client.java

public class Client {
    public static void main(String[] args) {
        //炒包菜
        //创建对象
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        //调用炒菜的功能
        baoCai.cookProcess();
    	
        System.out.println("=====================");
        
        ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
        caiXin.cookProcess();
    }
}

注意:为防止恶意操作,一般使用 final 来修饰模板方法。

4.优缺点

(1)优点

  • 提供了一种简单的扩展和定制算法的方式,通过子类可以灵活地实现算法中的具体步骤,同时保持了算法的整体结构的稳定性
  • 通过抽象父类和具体子类的分离,提高了代码的可维护性和可复用性,减少了重复代码的编写。
  • 通过模板方法的设计,可以很好地提供一个固定的算法框架,从而降低了算法的设计和实现的复杂度

(2)缺点

  • 如果模板方法本身需要的改动较多,可能需要修改抽象父类或者大量的子类,导致系统的扩展性变差
  • 如果算法的结构较为复杂,可能会导致父类的设计过于庞大和复杂,难以理解和维护

5.使用场景

(1)模板方法模式适用于以下场景:

  • 算法的结构固定,但是某些具体步骤的实现可能变化:当一个算法的整体结构是固定的,但是其中某些具体步骤的实现可能会有所变化时,可以使用模板方法模式。通过在抽象类中定义算法的骨架,将可变的部分延迟到子类中实现,从而实现算法的灵活扩展和定制。
  • 多个类具有相似的行为:当多个类具有相似的行为,但是具体实现又有所不同时,可以使用模板方法模式。通过将这些相似的行为抽象到父类中,子类可以分别实现各自的具体实现,从而提高代码的复用性和可维护性。
  • 需要控制算法的执行流程:当需要控制算法的执行流程,确保算法的每个步骤按照特定的顺序进行时,可以使用模板方法模式。通过在抽象类中定义算法的步骤和顺序,确保算法的正确执行,同时允许子类实现具体步骤,以满足特定需求。
  • 提供一个框架或工具的扩展点:当提供一个框架或工具,并希望允许用户定制某些行为时,可以使用模板方法模式。通过在框架或工具中定义抽象类和模板方法,用户可以通过继承抽象类并实现具体步骤,来定制框架或工具的行为。

(2)总的来说,模板方法模式适用于具有固定算法结构但可变实现步骤、多个类具有相似行为、需要控制算法执行流程以及需要提供扩展点的场景。该模式可以提高代码的复用性、可维护性和灵活性。

6.JDK 源码解析

6.1.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;
        }
		//调用了无参的 read() 方法,该方法是每次读取一个字节数据
        int c = 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;
    }
}

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

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

6.2.AbstractQueuedSynchronizer

同步器 (AbstractQueuedSynchronizer) 的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。举个例子,AQS 中需要重写的方法 tryAcquire

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
}

ReentrantLockNonfairSync(继承自 AQS)会重写该方法为:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

而 AQS 中的模板方法 acquire()

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

会调用 tryAcquire 方法,而此时当继承 AQS 的 NonfairSync 调用模板方法 acquire 时就会调用已经被 NonfairSync 重写的 tryAcquire 方法。具体关系如下图所示:

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

【代码随想录 | Leetcode | 第三天】数组 | 滑动窗口 | 209

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏&#xff0c;今天将为大家带来滑动窗口的分享✨ 目录 前言209. 长度最小的子数组总结 209. 长度最小的子数组 ✨题目链接点这里 给定一个含有 n 个正整数的数组和一个正整数target。找出该数组中满足其和 ≥ target 的长度…

docker在arm64架构ubuntu系统的安装

卸载可能存在的旧版本 sudo apt remove docker docker-engine docker-ce docker-io安装依赖使apt可通过HTTPS下载包 sudo apt update && apt install -y apt-tranport-https ca-certificates curl software-properties-commonapt-transport-https用于支持通过HTTPS协…

如何设计光场2.0(聚焦型光场相机)系统参数

1. 系统参数设计 目前的硬件系统的现状&#xff1a;主透镜50mm&#xff0c;MLA&#xff1a;15*15&#xff0c;d0.5mm&#xff0c;f15mm&#xff0c;s4.8um 开普勒型光场系统&#xff1a; 首先我们需要确定系统的M&#xff0c;M参数表示单个位置的点能被多少个小微透镜成像&am…

C++【哈希表的完善及封装】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、哈希表的完善1.1、拷贝与赋值1.2、优化&#xff1a;哈希函数1.3、优化&am…

带你快速了解字符(串)函数

​ ⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f496; 持续更文&#xff0c;谢谢大家支持 &#x1f496; 文章目录 本文重点1. strlen函数1.1 模拟实现 2. strcpy函数2.1 模拟实现 3. strcat函数3.1 模拟实现 4. strcmp函…

基于linux下的高并发服务器开发(第一章)- 目录遍历函数

10 / 目录遍历函数 // 打开一个目录 #include <sys/types.h> #include <dirent.h>DIR *opendir(const char *name); 参数&#xff1a; - name: 需要打开的目录的名称 返回值&#xff1a; DIR * 类型&#xff0c;理解为目录流 错误…

Hcip第五次作业----BGP联邦综合实验

配置IP地址 r1 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 12.0.0.1 24 [r1-GigabitEthernet0/0/0]int lo0 [r1-LoopBack0]ip add 192.168.1.1 24 [r1-LoopBack0]int lo1 [r1-LoopBack1]ip add 10.0.0.1 24 r2 [r2]int g0/0/0 [r2-GigabitEthernet0/0/0]ip add 12.0.0.2…

Orangepi Zero2 基于官方外设开发(二)

一、OLED屏显示-IIC协议 1、相关介绍 IIC及OLED相关内容请参考以下文章&#xff1a; IIC协议_单行梦想家的博客-CSDN博客 OLED显示屏_单行梦想家的博客-CSDN博客 2、OrangePi的IIC接口 由原理图可知&#xff0c;Orange Pi Zero 2 可用的 i2c 为 i2c3 Linux系统启动后&…

针对我国水资源量设计的农田灌溉收费管理平台

安科瑞虞佳豪 降水量 2013年&#xff0c;全国平均降水量661.9mm&#xff0c;折合降水总量62674.4亿立方米&#xff0c;比常年值偏多3.0%。从水资源分区看&#xff0c;松花江、辽河、海河、黄河、淮河、西北诸河6个水资源一级区&#xff08;以下简称北方6区&#xff09;平均降水…

Ubuntu22.04密码忘记怎么办 Ubuntu重置root密码方法

在Ubuntu 22.04 或其他更高版本上不小心忘记root或其他账户的密码怎么办&#xff1f; 首先uname -r查看当前系统正在使用的内核版本&#xff0c;记下来 前提&#xff1a;是你的本地电脑&#xff0c;有物理访问权限。其他如远程登录的不适用这套改密方法。 通过以下步骤&#…

基于GIS的生态敏感性评价与产业路径选择研究:以江西省吉安市为例

导读: 确立绿水青山就是金山银山的理念,建立生态经济体系,是新时代生态环境保护与经济发展的协调之道。对产业规划而言,与生态同行,构建绿色产业体系,是推动地区高质量发展的根本要求。鉴于此,文章从实证角度出发,以江西省吉安市为研究对象,采用生态敏感性评价方法,选…

rv1126板子挂载nfs、拉流测试、固件烧写

目录 一、Ubuntu NFS服务器设置设置有线网卡桥接模式安装NFS并启动NFS二、烧录固件三、配置ipwindow上安装adb修改板子的ip,与ubuntu的桥接网卡同网段四、通过ssh登录开发板五、板子上挂载nfs六、测试拉流七、遇见的问题问题一问题二一、Ubuntu NFS服务器设置 设置有线网卡桥…

openwrt上ipv6 ddns 解析

之前写过一个教程如何在openwrt上使用docker版本的ddns解析工具&#xff0c;使用docker的好处是部署简单&#xff0c;支持的域名种类多&#xff1b;openwrt的docker环境安装起来也很方便&#xff0c;尤其有不少编译好的&#xff0c;带docker环境的镜像可以用&#xff0c;例如笔…

什么是向量数据库?

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

via24种人格力量之学习的力量,爱学习的特征和益处

人格力量是一种可支配的价值观&#xff0c;通常是正向的&#xff0c;有益于学习工作和日常生活的&#xff0c;在via 24种人格力量测试中&#xff0c;爱学习是其中的维度之一&#xff0c;爱学习的人格特征会让人终生受益&#xff0c;但是凡事都适度&#xff0c;如果过度的痴迷于…

检测到会话cookie中缺少HttpOnly属性

绿盟科技"远程安全评估系统"安全评估报告,这里记录一下处理过程。 检测到会话cookie中缺少HttpOnly属性 详细描述 会话cookie中缺少HttpOnly属性会导致攻击者可以通过程序(JS脚本、Applet等)获取到用户的cookie信息&#xff0c;造成用户cookie信息泄露&#xff0c…

Java 中 注解是什么?如何使用

当谈到 Java 中的注解时&#xff0c;我们指的是 Java 5 中引入的一种元数据机制&#xff0c;它允许我们在代码中添加元数据信息并在运行时读取它们。在本文中&#xff0c;我们将深入探讨 Java 中的注解&#xff0c;包括它们是什么、如何使用它们以及一些示例代码。 注解是什么&…

浅读《商用密码应用性评估白皮书》

浅读《商用密码应用性评估白皮书》 密码的重要性商用密码概念商用密码典型应用场景&#xff08;一&#xff09;电信和互联网领域&#xff08;二&#xff09;工业互联网领域&#xff08;三&#xff09;车联网领域&#xff08;四&#xff09;物联网领域&#xff08;五&#xff09…

路径规划算法:基于驾驶训练优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于驾驶训练优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于驾驶训练优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

Redis实战篇(五)

8、达人探店 8.1 达人探店-发布探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标题、文字、图片等tb_blog_comments&#xff1a;其他用户对探店笔记的评价 具体发布流…