设计模式浅析(九) ·模板方法模式

设计模式浅析(九) ·模板方法模式

日常叨逼叨

java设计模式浅析,如果觉得对你有帮助,记得一键三连,谢谢各位观众老爷😁😁


模板方法模式

概念

模板方法模式(Template Method Pattern)在Java中是一种非常实用的设计模式,它允许我们在一个方法中定义一个算法的骨架,同时允许子类在不改变算法结构的情况下重新定义某些步骤的具体内容。这种模式非常适合于那些算法的整体步骤固定,但某些步骤的具体实现可能因需求不同而有所变化的场景。

组成
  1. 抽象类(Abstract Class)
    • 定义了一个或多个抽象方法,这些抽象方法由具体子类来实现。
    • 包含一个模板方法,这个方法定义了算法的骨架,并调用了在抽象类中定义的抽象方法。
    • 模板方法通常被声明为final,以防止子类覆盖它。
    • 模板方法通常还包含一些普通方法,这些方法可以被具体子类直接使用或覆盖。
  2. 具体子类(Concrete Subclasses)
    • 实现了抽象类中定义的抽象方法,提供了这些抽象方法的具体实现。
    • 通过继承抽象类,子类可以重写普通方法,以提供不同的实现。
    • 子类可以通过调用父类的模板方法来执行算法。
  3. 客户端
    • 客户端代码创建具体类的实例,并调用模板方法来执行算法。

案例

继续以一家饮品店为例子:

A饮品店有咖啡(coffee)和茶(tea)两种饮品,对于其制作方法如下:

  • 咖啡冲泡法

    • (1)把水煮沸

    • (2)用沸水冲泡咖啡

    • (3)把咖啡倒进杯子

    • (4)加糖和牛奶

  • 茶水冲泡法

    • (1)把水煮沸

    • (2)用沸水浸泡茶叶

    • (3)把茶倒进杯子

    • (4)加柠檬

让我们扮演冲泡师傅,快速的创建茶水和咖啡

//coffee
public class Coffee {void prepareRecipe() {boilWater();brewCoffeeGrinds();pourInCup();addSugarAndMilk();}public void boilWater() {System.out.println("step 1 Boiling water");}public void brewCoffeeGrinds() {System.out.println("step 2 Dripping Coffee through filter");}public void pourInCup() {System.out.println("step 3 Pouring into cup");}public void addSugarAndMilk() {System.out.println("step 4 Adding Sugar and Milk");}
}
//Tea
public class Tea {void prepareRecipe() {boilwater();steepTeaBag();pourInCup();addLemon();}public void boilwater() {System.out.println("step 1 Boiling water");}public void steepTeaBag() {System.out.println("step 2 Steeping the tea");}public void addLemon() {System.out.println("step 3 Adding Lemon");}public void pourInCup() {System.out.println("step 4 Pouring into cup");}
}

仔细观察上述的代码,会发现coffee和tea 除了第2步骤和第4步骤不一样外,其他的步骤几乎一致,代码存在者较高的冗余。是不是可以将一些代码抽取出来?

那么第一次抽取之后,类图可能会变成这个样子

在这里插入图片描述

但是仔细想一想上面的制作方法

在这里插入图片描述

让我们来思考这一点:浸泡(steep)和冲泡(brew)差异其实不大。所以我们给它一个新的方法名称,比方说brew(),然后不管是泡茶或冲泡咖啡我们都用这个名称。

类似地,加糖和牛奶也和加柠檬很相似:都是在饮料中加入调料,由此,我们再做一次公共的提取。

首先,我们定义一个抽象类DrinksMake,在其中定义一个final的方法,规定具体的步骤:

abstract class DrinksMake {//模板方法public final void makeDrinks() {//step 1 烧水boiledWater();//step 2 冲泡brew();//step3 倒出到杯子中pourInCup();//step4 添加配料addTheIngredients();}protected abstract void addTheIngredients();protected abstract void brew();protected void boiledWater() {System.out.println("step1:boiled water!");}protected void pourInCup() {System.out.println("step3: pourInCup !");}}

然后,我们定义制作咖啡的具体实现以及茶水的具体实现

public class CoffeeMake extends DrinksMake {@Overrideprotected void addTheIngredients() {System.out.println("step 4 : add Sugar and Milk into Coffee");}@Overrideprotected void brew() {System.out.println("step 2 : brew Coffee");}
}
public class TeaMake extends DrinksMake {@Overrideprotected void addTheIngredients() {System.out.println("step 4 : add Lemon into tea");}@Overrideprotected void brew() {System.out.println("step 2 : brew tea");}
}

那么对于客户端来说,我们分别制作一杯咖啡和一杯茶

public class Client {public static void main(String[] args) {TeaMake teaMake = new TeaMake();CoffeeMake coffeeMake = new CoffeeMake();teaMake.makeDrinks();coffeeMake.makeDrinks();}
}

运行结果:

step1:boiled water!
step 2 : brew tea
step3: pourInCup !
step 4 : add Lemon into tea
step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
step 4 : add Sugar and Milk into Coffee

Process finished with exit code 0

至此我们实现了设计模式中的模板方法模式

模板方法模式中的钩子hook

我们在上述的抽象父类中再加入一个方法

void hook(){}
//但是这个方法暂时什么都不做

我们也可以有默认不做事的方法,我们称这神方法为hook(钩子)。子类可以视情况决定要不要覆盖它们。接下来就会知道钩子的实际用途。

试想一下,如果在step4,添加小料的时候,是不是应该询问一下顾客的意见?不能问不问就为顾客添加一些小料,这个是不太合理的,那么我们的代码可以这么修改:

abstract class DrinksMake {//模板方法public final void makeDrinks() {//step 1 烧水boiledWater();//step 2 冲泡brew();//step3 倒出到杯子中pourInCup();//step4 添加配料if (ifNeedIngredients()) {addTheIngredients();}}protected abstract void addTheIngredients();protected abstract void brew();protected void boiledWater() {System.out.println("step1:boiled water!");}protected void pourInCup() {System.out.println("step3: pourInCup !");}/**** 钩子方法,子类可以选择重写或者不重写*/boolean ifNeedIngredients() {return true;}}

创建一个类似于钩子的方法,默认返回是添加小料,可以在子类中进行重写进行相关的操作,我们可以在子类中进行询问顾客是否需要小料

public class CoffeeMakeWithHook extends DrinksMake {@Overrideprotected void addTheIngredients() {System.out.println("step 4 : add Sugar and Milk into Coffee");}@Overrideprotected void brew() {System.out.println("step 2 : brew Coffee");}@Overrideboolean ifNeedIngredients() {String answer = getUserInput();if (answer.toLowerCase().startsWith("y")) {return true;} elsereturn false;}//  让用户输入他们对调料的决定。根据用户的输入返回tue或falseprivate String getUserInput() {String answer = null;System.out.print("would you like milk and sugar with your coffee (y/n) ?");BufferedReader in = new BufferedReader(new InputStreamReader(System.in));try {answer = in.readLine();} catch (IOException e) {System.err.println("IO error trying to read your answer");}if (answer == null) {return "no";}return answer;}
}

再次创建客户端代码,进行coffeewithhook的调用

public class Client {public static void main(String[] args) {CoffeeMakeWithHook coffeeMake = new CoffeeMakeWithHook();coffeeMake.makeDrinks();}
}

运行结果如下:

不需要小料:

step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
would you like milk and sugar with your coffee (y/n) ?n

Process finished with exit code 0

需要小料:

step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
would you like milk and sugar with your coffee (y/n) ?y
step 4 : add Sugar and Milk into Coffee

Process finished with exit code 0

优缺点

优点:
  1. 代码复用:模板方法模式允许你定义一个算法的骨架,而将一些步骤延迟到子类中。这允许子类在不改变算法结构的情况下重定义某些步骤的具体内容,从而实现了代码的复用。
  2. 扩展性:由于模板方法模式将算法中的固定部分与可变部分分离,这使得子类可以通过扩展来增加新的行为,从而提高了代码的扩展性。
  3. 封装性:通过将不变的部分封装在父类中,模板方法模式隐藏了实现细节,只暴露必要的接口给子类。这有助于减少子类的耦合度。
  4. 行为控制:模板方法模式允许通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行,从而提供了一种反向控制结构。
缺点:
  1. 代码可读性:由于模板方法模式中的钩子方法和抽象方法的调用顺序可能比较复杂,这可能会增加代码的阅读难度,尤其是对于新手来说。
  2. 继承的缺点:模板方法模式依赖于继承来实现代码复用,而继承本身有一些固有的缺点,如破坏封装性、子类与父类之间的强耦合等。
  3. 扩展限制:如果父类中的模板方法被声明为final,那么子类将无法覆盖该方法,这可能会限制子类的扩展性。

总的来说,Java模板方法模式在需要定义一系列复杂算法时非常有用,它能够有效地实现代码复用和扩展。然而,它也有一些缺点需要注意,如代码可读性和继承的局限性等。因此,在使用模板方法模式时,需要根据具体情况权衡其优缺点。


代码相关代码可以参考 代码仓库🌐

ps:本文原创,转载请注明出处


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HP笔记本电脑如何恢复出厂设置?这里提供几种方法

要恢复出厂设置Windows 11或10的HP笔记本电脑,你可以使用操作系统的标准方法。如果你运行的是早期版本,你可以使用HP提供的单独程序清除计算机并重新安装操作系统。 恢复出厂设置运行Windows 11的HP笔记本电脑​ 所有Windows 11计算机都有一个名为“重置此电脑”的功能,可…

Llama2模型的优化版本:Llama-2-Onnx

Llama2模型的优化版本:Llama-2-Onnx。 Llama-2-Onnx是Llama2模型的优化版本。Llama2模型由一堆解码器层组成。每个解码器层(或变换器块)由一个自注意层和一个前馈多层感知器构成。与经典的变换器相比,Llama模型在前馈层中使用了不…

uni-app原生api的promise化以解决异步等待问题分析

相信各位在进行uni-app开发的时候会遇到各种关于异步回调问题,例如要传code给后端以换取session_key,在这之前需要先调用 uni.login,所以执行的顺序是必须同步等待的。在写这篇文章之前对于整体的流程概念需要做一个梳理,以便能更…

普中51单片机学习(8*8LED点阵)

8*8LED点阵 实验代码 #include "reg52.h" #include "intrins.h"typedef unsigned int u16; typedef unsigned char u8; u8 lednum0x80;sbit SHCPP3^6; sbit SERP3^4; sbit STCPP3^5;void HC595SENDBYTE(u8 dat) {u8 a;SHCP1;STCP1;for(a0;a<8;a){SERd…

分布式事务之2、3段提交协议

二阶段提交协议 二阶段提交(Two-phaseCommit)是在计算机网络以及数据库领域内&#xff0c;为了使分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。 在分布式系统中&#xff0c;每个节点虽然可以知晓自己的操作是成功或者失败&#xff0c;却无法知道其…

项目登录方案选型

一.Cookie + Session 登录 大家都知道,HTTP 是一种无状态的协议。无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求返回数据,但不会记录任何信息。为了解决 HTTP 无状态的问题,出现了 Cookie。Co…

[嵌入式系统-33]:RT-Thread -18- 新手指南:三种不同的版本、三阶段学习路径

目录 前言&#xff1a;学习路径&#xff1a;入门学习-》进阶段学习》应用开发 一、RT-Thread版本 1.1 标准版 1.2 Nano 1.3 Smart版本 1.4 初学者制定学习路线 1.5 RT-Thread在线文档中心目录结构 1.6 学习和使用RT-Thread的三种场景 二、入门学习阶段&#xff1a;内…

面试redis篇-08数据淘汰策略

原理 当Redis中的内存不够用时,此时在向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称之为内存的淘汰策略。 Redis支持8种不同策略来选择要删除的key: noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是…

BTC网络 vs ETH网络

设计理念 BTC 网络 比特币是一种数字货币&#xff0c;旨在作为一种去中心化的、不受政府或金融机构控制的电子货币。其主要目标是实现安全的价值传输和储存&#xff0c;比特币的设计强调去中心化和抗审查。 ETH 网络 以太坊是一个智能合约平台&#xff0c;旨在支持分散的应…

thinkphp6定时任务

这里主要是教没有用过定时任务没有头绪的朋友, 定时任务可以处理一些定时备份数据库等一系列操作, 具体根据自己的业务逻辑进行更改 直接上代码 首先, 是先在 tp 中的 command 方法中声明, 如果没有就自己新建一个, 代码如下 然后就是写你的业务逻辑 执行定时任务 方法写好了…

Laravel03 路由到控制器与连接数据库

Laravel03 路由到控制器与连接数据库 1. 路由到控制器2. 连接数据库 1. 路由到控制器 如下图一些简单的逻辑处理可以放在web.php中&#xff0c;也就是路由的闭包函数里面。但是大的项目&#xff0c;我们肯定不能这么写。 为什么保证业务清晰好管理&#xff0c;都应该吧业务逻辑…

IP 电话

1 IP 电话概述 IP 电话是在互联网上传送多媒体信息。 多个英文同义词&#xff1a; VoIP (Voice over IP) Internet Telephony VON (Voice On the Net) 1.1 狭义的和广义的 IP 电话 狭义的 IP 电话&#xff1a;指在 IP 网络上打电话。 广义的 IP 电话&#xff1a;不仅仅是…

在Pycharm中运行Django项目如何指定运行的端口

方法步骤&#xff1a; 打开 PyCharm&#xff0c;选择你的 Django 项目。在菜单栏中&#xff0c;选择 “Run” -> “Edit Configurations...”。在打开的 “Run/Debug Configurations” 对话框中&#xff0c;选择你的 Django server 配置&#xff08;如果没有&#xff0c;你…

力扣链表篇

以下刷题思路来自代码随想录以及官方题解 文章目录 203.移除链表元素707.设计链表206.反转链表24.两两交换链表中的节点19.删除链表的倒数第N个节点面试题 02.07. 链表相交142.环形链表II 203.移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链…

pytorch -- torch.nn下的常用损失函数

1.基础 loss function损失函数&#xff1a;预测输出与实际输出 差距 越小越好 - 计算实际输出和目标之间的差距 - 为我们更新输出提供依据&#xff08;反向传播&#xff09; 1. L1 torch.nn.L1Loss(size_averageNone, reduceNone, reduction‘mean’) 2. 平方差&#xff08;…

openai.CLIP多模态模型简介

介绍 OpenAI CLIP&#xff08;Contrastive Language–Image Pretraining&#xff09;是一种由OpenAI开发的多模态学习模型。它能够同时理解图像和文本&#xff0c;并在两者之间建立联系&#xff0c;实现了图像和文本之间的跨模态理解。 如何工作 CLIP模型的工作原理是将来自…

npm install 失败,需要node 切换到 对应版本号

npm install 失败 原本node 的版本号是16.9&#xff0c;就会报以上错误 node版本问题了&#xff0c;我切到这个版本&#xff0c;报同样的错。降一下node&#xff08;14.18&#xff09;版本就好了 具体的方法&#xff1a;&#xff08;需要在项目根目录下切换&#xff09; 1. …

Linux使用Docker部署在线协作白板WBO并结合内网穿透发布公网远程访问

文章目录 前言1. 部署WBO白板2. 本地访问WBO白板3. Linux 安装cpolar4. 配置WBO公网访问地址5. 公网远程访问WBO白板6. 固定WBO白板公网地址 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用…

蜜雪冰城、尚客优、塔斯汀.....下沉同花顺为何能红?

热辣滚烫的龙年春节假期&#xff0c;以一组漂亮的数据收尾&#xff1a;4.74亿人次出游&#xff0c;花费6326.87亿元。这是春节假期国内旅游的“战报”。出游需求高涨&#xff0c;带动在线旅游平台生意火爆。年前就“卷得飞起”的各地文旅&#xff0c;春节假期收获丰厚回报。 有…

四、矩阵的分类

目录 1、相等矩阵 2、同形矩阵 3、方阵&#xff1a; 4、负矩阵、上三角矩阵、下三角矩阵&#xff1a; 5、对角矩阵&#xff1a;是方阵 ​编辑7、单位矩阵&#xff1a;常常用 E或I 来表示。它是一个方阵 8、零矩阵&#xff1a; 9、对称矩阵&#xff1a;方阵 1、相等矩阵 …