行为型模式 - 模板方法模式

news2024/12/30 2:35:40

概述

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

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

定义:

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

结构

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

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

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

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

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

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

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

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

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

案例实现

【例】炒菜

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

 代码如下:

public abstract class AbstractClass {
    
    public final void cookProcess() {
        //第一步:倒油
        this.pourOil();
        //第二步:热油
        this.heatOil();
        //第三步:倒蔬菜
        this.pourVegetable();
        //第四步:倒调味料
        this.pourSauce();
        //第五步:翻炒
        this.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("炒啊炒啊炒到熟啊");
    }
}

public class ConcreteClass_BaoCai extends AbstractClass {

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

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

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

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

public class Client {
    public static void main(String[] args) {
        //炒手撕包菜
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        baoCai.cookProcess();

        //炒蒜蓉菜心
        ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
        caiXin.cookProcess();
    }
}

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

优缺点

优点:

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制

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

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。

  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。

  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

JDK源码解析

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/772035.html

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

相关文章

JAVA ---- 经典排序算法

目录 一. 插入排序 1. 直接插入排序 代码演示 2.希尔排序( 缩小增量排序 ) 二. 选择排序 1.直接选择排序 代码&#xff1a; 2. 堆排序 代码 三. 交换排序 1. 冒泡排序 代码 2. 快速排序 代码&#xff08;有注释&#xff09;&#xff1a; 动图来自网…

Mysql教程(四):DML学习

Mysql教程&#xff08;四&#xff09;&#xff1a;DML学习 前言 DML-介绍 DML英文全称是Data Manipulation Language数据库操作语言&#xff0c;用来对数据库中表的数据记录进行增删改查。 添加数据&#xff08;INSERT&#xff09;修改数据&#xff08;UPDATE&#xff09;删除…

Java 串口通讯 Demo

为什么写这篇文章 之前职业生涯中遇到的都是通过tcp协议与其他设备进行通讯&#xff0c;而这个是通过串口与其他设备进行通讯&#xff0c;意识到这里是承重板的连接&#xff0c;但实际上比如拉力、压力等模拟信号转换成数字信号的设备应该是有相当一大部分是通过这种方式通讯的…

为你精选5款体验极佳的原型设计工具!

在绘制原型图的过程中&#xff0c;使用一款的简单易操作的原型设计工具是非常重要的&#xff0c;本文精选了5款好用的原型工具与大家分享&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计是国内很多设计师都在用的原型设计工具&#xff0c;同时它也是国产的原型设…

JMeter正则表达式提取器和JSON提取器基础用法,小白必会!

最近在利用JMeter做接口自动化测试&#xff0c;正则表达式提取器和JSON提取器用的还挺多&#xff0c;想着分享下&#xff0c;希望对大家的接口自动化测试项目有所启发。 在 JMeter 中&#xff0c;正则表达式和 JSON 提取器都是用于从响应数据中提取所需内容&#xff0c;但它们的…

界面控件Telerik UI for WinForms R2 2023——发布全新的热图控件

Telerik UI for WinForms拥有适用Windows Forms的110多个令人惊叹的UI控件。所有的UI for WinForms控件都具有完整的主题支持&#xff0c;可以轻松地帮助开发人员在桌面和平板电脑应用程序提供一致美观的下一代用户体验。 在本文中&#xff0c;我们将揭秘一下Telerik UI for W…

vue数组对象快速获取最大值和最小值(linq插件各种常用好用方法),提高开发效率

需求&#xff1a;因后端传入的数据过多&#xff0c;前端需要在数组中某一值的的最大值和最小值计算&#xff0c;平常用的最多的不就是遍历之后再比对吗&#xff0c;或者用sort方法等实现&#xff0c;同事交了我一招&#xff0c;一句话就可以获取到数组对象中最大值和最小值&…

【Docker】Docker基本概念

Docker基本概念 1.Docker概述1.1 Docker是什么&#xff1f;1.2 Docker的宗旨1.3 容器的优点1.4 Docker与虚拟机的区别1.5 容器在内核中支持的两种技术1.6 namespace的六大类型 2.Docker核心概念2.1 镜像2.2 容器2.3 仓库 3. 知识点总结3.1 Docker是什么&#xff1f;3.2 容器和虚…

Fortinet Accelerate 2023·中国区巡展收官丨让安全成就未来

7月18日&#xff0c;2023 Fortinet Accelerate Summit在上海成功举办&#xff01;这亦象征着“Fortinet Accelerate2023中国区巡展”圆满收官。Fortinet携手来自多个典型行业的百余位代表客户&#xff0c;以及亚马逊云科技、Telstra - PBS 太平洋电信、Tenable等多家生态合作伙…

RT-Thread 学习-Env开发环境搭建(一)

Env是什么 Env 是 RT-Thread 推出的开发辅助工具&#xff0c;针对基于 RT-Thread 操作系统的项目工程&#xff0c;提供编译构建环境、图形化系统配置及软件包管理功能。 其内置的 menuconfig 提供了简单易用的配置剪裁工具&#xff0c;可对内核、组件和软件包进行自由裁剪&…

PROFIBUS-DP主站转ETHERNET/IP网关ethernet有哪些协议

远创智控YC-DPM-EIP是自主研发的一款PROFIBUS-DP主站功能的通讯网关。该产品主要功能是将各种PROFIBUS-DP从站接入到ETHERNET/IP网络中。 1, 本网关连接到PROFIBUS总线中作为主站使用&#xff0c;连接到ETHERNET/IP总线中作为从站使用。 1.2 产品特点 ◆ PROFIBUS-DP/V0 协议…

AtcoderABC243场

A - Shampoo A - Shampoo ] 题目大意 高桥家有三个人&#xff1a;高桥、他的父亲和他的母亲。每个人每晚都在浴室洗头发。他们按照顺序使用AA、BB和CC毫升的洗发水。 问&#xff0c;今天早上瓶子里有VV毫升的洗发水。在不重新装满的情况下&#xff0c;谁会第一个用完洗发水洗头…

【Maven四】——maven聚合和继承

系列文章目录 Maven之POM介绍 maven命令上传jar包到nexus 【Maven二】——maven仓库 【Maven三】——maven生命周期和插件 聚合和继承 系列文章目录前言一、什么是maven的聚合和继承&why二、聚合三、继承1.可继承的POM元素2.依赖管理3.插件管理 四、聚合与继承的关系五、约…

java电子病历系统源码

电子病历系统采取结构化与自由式录入的新模式&#xff0c;自由书写&#xff0c;轻松录入。化实现病人医疗记录&#xff08;包含有首页、病程记录、检查检验结果、医嘱、手术记录、护理记录等等。&#xff09;的保存、管理、传输和重现&#xff0c;取代手写纸张病历。不仅实现了…

【Docker】Docker安装与操作

docker的安装与命令 一、安装 docker1. 安装依赖包2. 信息查看 二、Docker 镜像操作1. 搜索镜像2. 获取镜像3. 镜像加速下载4. 查看镜像相关信息5. 删除镜像6. 上传镜像7. 存出和载入镜像 三、Docker 容器操作1. 创建容器2. 查看容器3. 启动容器4. 停止容器5. 进入容器6. 容器与…

四、DML-4.小结

一、数据记录操作 1、添加数据 给指定字段添加数据 insert into employee(id, workno, name, gender, age, idcard,entrydate) values (1, 001,Itcast, 男, 18, 123456789012345678, 2023-12-12); INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...); 给全部…

Linux--使用者管理(job control)

Linux–使用者管理(job control) 文章目录 Linux--使用者管理(job control)前言一、任务管理(job control)二、&三、 将目前的任务丢到后台中暂停 -- ctrlz四、jobs -- 查看目前的后台任务状态五、fg -- 将后台任务拿到前台来处理六、bg -- 让任务在后台下的状态变为运行中…

LeetCode 1493. 删掉一个元素以后全为 1 的最长子数组 - 二分 + 滑动窗口

删掉一个元素以后全为 1 的最长子数组 提示 中等 90 相关企业 给你一个二进制数组 nums &#xff0c;你需要从中删掉一个元素。 请你在删掉元素的结果数组中&#xff0c;返回最长的且只包含 1 的非空子数组的长度。 如果不存在这样的子数组&#xff0c;请返回 0 。 提示 1&a…

2023-07-18力扣今日二题-太难了吧

链接&#xff1a; LCP 75. 传送卷轴 题意&#xff1a; 给一个正方形迷宫&#xff0c;主角是A&#xff0c;每次可以上下左右走一格子&#xff0c;有四种类型的格子&#xff1a;墙、初始位置、魔法水晶、空地 另一个人B&#xff0c;可以传送一次A&#xff0c;只能在空地传送&…

Twisted Circuit

题目描述 输入格式 The input consists of four lines, each line containing a single digit 0 or 1. 输出格式 Output a single digit, 0 or 1. 题意翻译 读入四个整数 00 或者 11&#xff0c;作为如图所示的电路图的输入。请输出按照电路图运算后的结果。 感谢PC_DOS …