设计模式_行为型模式 -《模板方法模式》

news2024/11/26 7:28:25

设计模式_行为型模式 -《模板方法模式》

笔记整理自 黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式分为:

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 责任链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式

概述

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

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

定义

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

结构

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

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

案例实现

【例】炒菜

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

代码如下:

  • 抽象类(定义模板方法和基本方法)

    public abstract class AbstractClass {
        
        // 模板方法(声明为final 禁止子类修改)
        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 类就使用了模板方法模式。在 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方法(也就是子类的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 就是抽象类,而三参的这个方法 read(byte b[], int off, int len) 就是模板方法,它里面定义了算法的框架,多次调用无参的 read() 方法并把每一次获取到的字节数据存到数组中。故 read() 方法就是抽象方法,子类必须实现。

总结:

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

AQS中的模板方法设计

参考豪哥

在 Java 中模板方法设计模式典型应用之一就是 AQS 的设计,AQS就是一个抽象类,我们在自定义一些同步器时需要继承AQS(比如ReentrantLock等)并实现共享资源state如何获取和释放,至于具体线程入队阻塞以及如何被唤醒出队的逻辑,AQS已经帮我们实现好了

  • 举例:看一个 AQS 的模板方法 acquire()

    // 1.acquire()方法
    /*
     * tryAcquire() 留给子类实现
     * acquireQueued() AQS已经实现好了,即获取同步状态失败时的入队阻塞逻辑。
     */
    public final void acquire(int arg) {
       if (!tryAcquire(arg) &&
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           selfInterrupt();
    }
    
    // AQS.tryAcquire() 没有设计成抽象方法,而是直接抛出异常,具体是让子类实现如何获取同步状态。
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    
  • 看一下 ReentrantLock 是如何做的

    // ReentrantLock内部定义一个内部类Sync实现AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        
        // 重写了AQS留给我们的tryAcquire()
        protected final boolean tryAcquire(int acquires) {
              // 具体的实现 ...
        }
    }
    
  • AQS 的模板方法中留给子类自定义实现的方法

    // 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS操作设置同步状态
    protected boolean tryAcquire(int arg);
    
    // 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
    protected boolean tryRelease(int arg) 
        
    // 共享式获取同步状态,返回大于等于0的值,表示获取成功,反之获取失败
    protected int tryAcquireShared(int arg)   
    
    // 共享式释放同步状态
    protected boolean tryReleaseShared(int arg)    
    
    // 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占。    
    protected boolean isHeldExclusively()    
    

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

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

相关文章

测试开发 | AppCrawler 自动遍历测试实践(三):动手实操与常见问题汇总

上两篇文章介绍了自动遍历的测试需求、工具选择和 AppCrawler 的环境安装、启动及配置文件字段基本含义&#xff0c;这里将以实际案例更加细致的说明配置文件的用法和一些特殊场景的处理。 实操演示 常规使用 下面我们继续之前的例子&#xff0c;在雪球搜索框输入搜索内容后的页…

代码随想录算法训练营第六天 | 哈希表理论基础,242.有效的字母异位词,349. 两个数组的交集, 202. 快乐数,1. 两数之和

第五天 周日 休息~【提醒补坑&#xff1a;链表总结还没写】一、参考资料哈希表理论基础文章连接&#xff1a;https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html有效的字母异位词题目链接/文章讲解/视频讲解&#xff1a;https:…

使用批处理__更改ip

1、使用.bat进行处理 echo off rem 测试更改ip netsh int ip set address "以太网 2" static 10.10.2.1 255.255.255.0 10.10.2.254 1 pause&exit 备注其他 echo #设静态IP netsh interface ip set address name"本地连接" sourcestatic addr192.16…

大坝安全监测解决方案 水库大坝安全监测系统改造工程方案

平升电子大坝安全监测系统根据SL551-2012《土石坝安全监测技术规范》的整编要求&#xff0c;设置了变形监测、渗流监测、环境量监测。借助大坝安全监测系统可及时了解大坝的工作性态和水库可能存在的事故隐患&#xff0c;为大坝安全管理与水库运行调度提供了准确、及时的现场信…

ElasticSearch7.10配置Search-Guard之配置用户

ElasticSearch7.10配置Search-Guard之配置用户 配置sg_internal_user.yml 密码是&#xff1a;elastic jode:hash: $2y$12$nUzkcjdnufzvI1HlmN7xSuND3skGhmwV5le5IINejz.asMFpLYNRybackend_roles:- "hr_department"psmith:hash: $2y$12$nUzkcjdnufzvI1HlmN7xSuND3sk…

[标准库]STM32F103R8T6 标准库配置RCC时钟和超频

前言 这篇博客总结一下学习到的配置时钟的方法。 从启动文件来看&#xff0c;MCU复位之后&#xff0c;执行到SystemInit()这个函数之后&#xff0c;会进入系统初始化设置&#xff0c;比如根据当前的MCU型号进入不同的条件编译语句&#xff0c;再配置相应的寄存器初始值&#…

mysql之一条mysql语句时如何执行的

请把连接器的功能说明一下? 连接器负责建立客户端和mysql服务器之间的连接.当客户端在中断输入连接命令(mysql -h -u -p),传输层使用tcp协议,通过三次握手建立连接. 如果mysql服务服务没有启动,会报错: 如果mysql服务正常启动,完成三次握手,在传输层建立连接后,会进行密码验证…

【云原生】k8s 一键部署(ansible)

文章目录一、概述二、Ansible 部署1&#xff09;开启记录日志2&#xff09;去掉第一次连接ssh ask确认3&#xff09;配置hosts三、开始编排 ansible playbook1&#xff09;创建目录2&#xff09;节点初始化3&#xff09;安装 docker4&#xff09;安装 k8s 相关组件5&#xff09…

2022 年 12 月区块链操作系统的开发回顾

正在寻找区块链操作系统组件的最新进展&#xff1f;你找对地方了&#xff01;正如在我们的路线图文章中所描述的那样&#xff0c;我们一直在朝着定期且频繁的更新方向发展着&#xff0c;以便让我们的社区能够及时的了解到我们取得的进展&#xff0c;以及将区块链操作系统提升到…

新年喜报 再添殊荣 加速科技荣获浙江省“专精特新”企业认定

新年伊始&#xff0c;杭州市经济和信息化局公布了2022年度浙江省专精特企业名单。杭州加速科技有限公司&#xff08;以下简称“加速科技”&#xff09;凭借在技术创新、产品研发、精细化程度、经营能力、拓展潜力等多方面的优势&#xff0c;荣获浙江省“专精特新”企业荣誉称号…

一个人,一座城,你到底在乎什么?Python 爬虫告诉你!

大家好&#xff0c;我是安果&#xff01;有时候&#xff0c;我们想知道生活在这座城市的人每天交流的事情&#xff0c;然后对数据进行一些分析&#xff0c;方便我们更好地了解城市的特征及居民的需求以重庆为例&#xff0c;最火爆的论坛是购物狂&#xff0c;每天都有大量的帖子…

求解带不确定事件的FJSP的多目标强化学习框架

文献&#xff1a;Hao Wang, Junfu Cheng, Chang Liu, Yuanyuan Zhang, Shunfang Hu, Liangyin Chen,Multi-objective reinforcement learning framework for dynamic flexible job shop scheduling problem with uncertain events,Applied Soft Computing,Volume 131,2022,1097…

超级详细的python知识点及练习题(附答案)

今天咱们继续来学习python的小知识吖&#xff0c;上一次木有看的同学请看&#xff1a;python8大核心语句 作者&#xff1a;阿玥的小东东 学习&#xff1a;python&#xff0c;正在学习c 主页&#xff1a;阿玥的小东东 目录 1.复习及易错&#xff0c;快来学习&#xff01;&#…

基于python手撕实现BP 神经网络实现手写数字识别

本项目使用python实现全连接网络和梯度优化 方向传播并且实现了 手写数字识别项目: 神经网络 model 先介绍个三层的神经网络,如下图所示输入层(input layer)有三个 units( 为补上的 bias,通常设为

线程池ThreadPoolExecutor源码解析

参考视频 首先回顾一下创建线程等的三种方式 第一个是直接继承Thread类&#xff0c;重写run方法&#xff0c;这个其实内部也是继承了Runnable接口重写run方法。 比如&#xff1a; public class MyThread extends Thread{Overridepublic void run() {System.out.println("…

论文分享-《基于数据驱动多输出 ARMAX 建模的高炉十字测温中心温度》

1.简介 最近在学习研究NARMAX&#xff0c;故也分享下自己看的一篇论文。 2018 年 3 月 的《基于数据驱动多输出 ARMAX 建模的高炉十字测温中心温度》。主要是采用NARMAX模型进行预测&#xff0c;多输入多输出&#xff0c;有5个输出&#xff0c;预测中心五个点位的温度。下面讲…

计算机 - - - 局域网共享文件夹,局域网传输文件(待完善)

win10局域网共享文件夹 A电脑: 共享文件夹的电脑 B电脑: 访问共享文件夹的电脑 操作完成后, B电脑可以下载A电脑中的文件, B电脑可以修改删除, B电脑可以上传B电脑的文件到A电脑. A电脑 找到要共享的文件夹, 例如我要共享文档(E:), 我要把文档(E:)中的所有文件都让B电脑访问…

Python - 数据容器str(字符串)

目录 字符串的定义 字符串的常用操作 查找特定字符串的下标索引值 index 字符串的替换 replace 字符串的分割 split 字符串的规整操作 strip 统计字符串中某字符串的出现次数 count 统计字符串的长度 len 字符串切片 [起始下标:结束下标:步长] 字符串的定义 和其它容器…

银行案例分析:识别个人贷款客户画像,实现精准营销与风险防范

作为商业银行最主要的业务活动&#xff0c;也是收益最大的活动&#xff0c;贷款于银行的重要性不言而喻。又由于个人贷款是银行贷款不可或缺的一部分&#xff0c;那么了解个人贷款客户画像就有助于银行对客户进行精准销售和风险识别。 # 选手介绍 #张昊泽&#xff1a;亚利桑那州…

Pycharm入门搭建Django 项目

一、环境准备 1、pycharm版本 2、python版本 二、创建项目 击左上角的 File -> New Project 点击Create创建完成之后页面等待下载环境 查看Django的版本 python -m django --version 启动项目 python manage.py runserver 三、后记 在启动 Django 项目的时候我发现控制台…