当前位置: 首页 > news >正文

【Java设计模式-5】装饰模式:给咖啡加点“佐料”

今天咱们要探索一下Java世界里的装饰模式(Decorator Pattern)。为了让这个过程更加生动易懂,咱们就以大家都熟悉的咖啡饮品来举例吧,想象一下,你就是那个咖啡大师,要给顾客调制出各种独特口味的咖啡哦!
在这里插入图片描述

一、咖啡的基础:简单的一杯咖啡

在我们的咖啡世界里,首先得有一杯基础的咖啡呀。就好比是Java里的一个简单类,它具备最基本的功能——能让你尝到咖啡的原味。

// 抽象的咖啡类,定义了咖啡的基本行为(获取描述和价格)
abstract class Coffee {public abstract String getDescription();public abstract double getCost();
}// 具体的咖啡实现类,这里是简单的黑咖啡
class BlackCoffee extends Coffee {@Overridepublic String getDescription() {return "黑咖啡";}@Overridepublic double getCost() {return 2.0; // 假设黑咖啡的价格是2元}
}

看,这里我们有了一个抽象的 Coffee 类,它规定了所有咖啡都应该能告诉我们它的描述(是什么咖啡)以及价格。然后 BlackCoffee 就是最基础的那种黑咖啡啦,原汁原味,价格也相对简单。

二、装饰模式登场:给咖啡加点料

现在呢,顾客们可不会满足于仅仅只有黑咖啡呀,他们可能想要加糖、加冰或者加牛奶,来调出自己喜欢的口味。这时候,装饰模式就该闪亮登场啦!

装饰模式的核心思想就是在不改变原有对象(这里就是黑咖啡)结构的基础上,动态地给它添加一些额外的功能(比如加糖、加冰等)。

我们先创建一个抽象的装饰者类,它和咖啡类一样,也实现了 Coffee 接口,这样它就能“伪装”成一杯咖啡啦。

// 抽象的咖啡装饰者类,继承自Coffee类,可以用来装饰其他咖啡对象
abstract class CoffeeDecorator extends Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double getCost() {return decoratedCoffee.getCost();}
}

这个抽象装饰者类有一个很重要的成员变量 decoratedCoffee,它用来保存被装饰的那个咖啡对象哦。而且注意啦,它的 getDescriptiongetCost 方法默认是返回被装饰咖啡的描述和价格,因为我们还没开始添加额外的东西嘛。

三、具体的装饰者:糖、冰、牛奶来啦

接下来,咱们就可以创建具体的装饰者类啦,分别对应着加糖、加冰和加牛奶。

加糖装饰者

// 加糖装饰者类,给咖啡加糖
class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", 加了糖";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 0.5; // 假设糖的价格是0.5元}
}

看呀,当我们用 SugarDecorator 去装饰一杯咖啡(比如黑咖啡)的时候,它会在原来咖啡的描述后面加上“,加了糖”,而且价格也会相应地增加0.5元哦,就好像真的给咖啡加了一份甜蜜的魔法呢!

加冰装饰者

// 加冰装饰者类,给咖啡加冰
class IceDecorator extends CoffeeDecorator {public IceDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", 加了冰";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 0.3; // 假设冰的价格是0.3元}
}

哇哦,IceDecorator 一上场,咖啡就变得清凉爽口啦,描述里多了“,加了冰”,价格也稍微涨了一点点,毕竟冰块也是有成本的嘛。

加牛奶装饰者

// 加牛奶装饰者类,给咖啡加牛奶
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + ", 加了牛奶";}@Overridepublic double getCost() {return decoratedCoffee.getCost() + 1.0; // 假设牛奶的价格是1元}
}

嘿嘿,加了牛奶的咖啡感觉更加香浓醇厚了呢,描述变得更诱人,价格也因为加了牛奶而有所增加啦。

四、调制一杯独特的咖啡

现在,咱们就可以像个真正的咖啡大师一样,开始调制各种独特口味的咖啡啦!

public class CoffeeShop {public static void main(String[] args) {// 先制作一杯黑咖啡Coffee coffee = new BlackCoffee();System.out.println("您点了一杯:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");// 给黑咖啡加糖coffee = new SugarDecorator(coffee);System.out.println("现在您的咖啡变成了:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");// 再加冰coffee = new IceDecorator(coffee);System.out.println("哇哦,又加了冰,现在是:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");// 最后再加牛奶coffee = new MilkDecorator(coffee);System.out.println("终极版咖啡来啦:" + coffee.getDescription() + ",价格是:" + coffee.getCost() + "元");}
}

运行上面的代码,你就会看到一杯普通的黑咖啡是如何一步步变成一杯超级豪华、口味独特的咖啡的哦。就像这样:

您点了一杯:黑咖啡,价格是:2.0元
现在您的咖啡变成了:黑咖啡, 加了糖,价格是:2.5元
哇哦,又加了冰,现在是:黑咖啡, 加了糖, 加了冰,价格是:2.8元
终极版咖啡来啦:黑咖啡, 加了糖, 加了冰, 加了牛奶,价格是:3.8元

是不是很有趣呀?通过装饰模式,我们可以非常灵活地根据顾客的需求,给咖啡添加各种各样的配料,而且每添加一种配料,咖啡的描述和价格都会相应地发生变化,就好像真的在现实生活中的咖啡店里调制咖啡一样呢!

五、装饰模式在SpringBoot中的应用场景

聊完了装饰模式的基本概念和示例,咱们再来说说它在SpringBoot这个强大的框架中是怎么大展身手的吧。

场景一:日志增强

在一个SpringBoot应用中,我们经常需要记录各种操作的日志。假设我们有一个简单的服务接口 UserService,它提供了一些用户相关的操作方法,比如 addUser()(添加用户)、updateUser()(更新用户信息)等等。

public interface UserService {void addUser(User user);void updateUser(User user);
}public class UserServiceImpl implements UserService {@Overridepublic void addUser(User user) {// 实际添加用户的逻辑System.out.println("添加用户:" + user.getName());}@Overridepublic void updateUser(User user) {// 实际更新用户信息的逻辑System.out.println("更新用户:" + user.getName());}
}

现在,我们想要在每个方法调用前后都记录详细的日志,包括方法名、传入的参数、执行时间等等。如果直接在 UserServiceImpl 类里面添加日志记录的代码,会让这个服务类变得很杂乱,而且不符合单一职责原则。这时候,装饰模式就可以派上用场啦!

我们可以创建一个日志装饰器 LoggingDecorator,它实现了 UserService 接口,并且持有一个 UserService 的引用。

public class LoggingDecorator implements UserService {private final UserService userService;public LoggingDecorator(UserService userService) {this.userService = userService;}@Overridepublic void addUser(User user) {long startTime = System.currentTimeMillis();System.out.println("开始执行 addUser 方法,参数:" + user);userService.addUser(user);long endTime = System.currentTimeMillis();System.out.println("addUser 方法执行完毕,耗时:" + (endTime - startTime) + " 毫秒");}@Overridepublic void updateUser(User user) {long startTime = System.currentTimeMillis();System.out.println("开始执行 updateUser 方法,参数:" + user);userService.updateUser(user);long endTime = System.currentTimeMillis();System.out.println("updateUser 方法执行完毕,耗时:" + (endTime - startTime) + " 毫秒");}
}

然后,在配置SpringBoot的Bean时,我们可以这样来使用这个装饰器:

@Configuration
public class AppConfig {@Beanpublic UserService userService() {UserService userServiceImpl = new UserServiceImpl();return new LoggingDecorator(userServiceImpl);}
}

这样,当我们在其他地方注入 UserService 并调用它的方法时,就会自动记录详细的日志啦,而且 UserServiceImpl 类本身的代码依然保持简洁,专注于实现用户服务的核心逻辑。

场景二:权限验证增强

再比如,我们的应用中有一些需要权限验证的接口,只有具有特定权限的用户才能访问。假设我们有一个 OrderService,它提供了一些订单相关的操作方法,比如 createOrder()(创建订单)、cancelOrder()(取消订单)等等。

public interface OrderService {void createOrder(Order order);void cancelOrder(Order order);
}public class OrderServiceImpl implements OrderService {@Overridepublic void createOrder(Order order) {// 实际创建订单的逻辑System.out.println("创建订单:" + order.getOrderId());}@Overridepublic void cancelOrder(Order order) {// 实际取消订单的逻辑System.out.println("取消订单:" + order.getOrderId());}
}

现在,我们想要在调用这些订单操作方法之前,先进行权限验证。同样的,我们可以创建一个权限验证装饰器 PermissionDecorator

public class PermissionDecorator implements OrderService {private final OrderService orderService;public PermissionDecorator(OrderService orderService) {this.orderService = orderService;}@Overridepublic void createOrder(Order order) {if (hasPermission()) { // 这里假设已经有一个方法来判断是否有权限orderService.createOrder(order);} else {throw new RuntimeException("没有权限创建订单");}}@Overridepublic void cancelOrder(Order order) {if (hasPermission()) {orderService.cancelOrder(order);} else {throw new RuntimeException("没有权限取消订单");}}private boolean hasPermission() {// 实际的权限验证逻辑,这里简单返回true模拟有权限return true;}
}

然后,在SpringBoot的配置中这样使用:

@Configuration
public class AppConfig {@Beanpublic OrderService orderService() {OrderService orderServiceImpl = new OrderServiceImpl();return new PermissionDecorator(orderServiceImpl);}
}

这样,每次调用订单服务的方法时,都会先进行权限验证,如果没有权限就会抛出异常,而 OrderServiceImpl 类本身不需要关心权限验证的事情,只专注于订单业务逻辑的实现。

通过这两个例子,我们可以看到在SpringBoot中,装饰模式可以很好地帮助我们在不修改原有业务逻辑类的基础上,对其功能进行增强,比如添加日志记录、权限验证等额外的职责,让我们的代码更加清晰、可维护和可扩展。

六、总结一下

好啦,咱们这次探索了Java设计模式中的装饰模式。这个模式的优点可不少哦,它让我们可以在不修改原有类的基础上,动态地扩展对象的功能,非常符合开闭原则(对扩展开放,对修改关闭)。就像我们给咖啡添加配料一样,不需要去改动原来的黑咖啡类,只需要创建新的装饰者类就可以啦。


相关文章:

【Java设计模式-5】装饰模式:给咖啡加点“佐料”

今天咱们要探索一下Java世界里的装饰模式(Decorator Pattern)。为了让这个过程更加生动易懂,咱们就以大家都熟悉的咖啡饮品来举例吧,想象一下,你就是那个咖啡大师,要给顾客调制出各种独特口味的咖啡哦&…...

C++ using(八股总结)

using作用: 类型别名using声明using指示 类型别名 using 可以用来创建类型别名,替代传统的 typedef。这在定义复杂类型时尤其有用,例如模板类型。 // 使用 typedef 创建类型别名 typedef long long ll;// 使用 using 创建类型别名 using …...

《分布式光纤传感:架设于桥梁监测领域的 “智慧光网” 》

桥梁作为交通基础设施的重要组成部分,其结构健康状况直接关系到交通运输的安全和畅通。随着桥梁建设规模的不断扩大和服役年限的增长,桥梁结构的安全隐患日益凸显,传统的监测方法已难以满足对桥梁结构健康实时、全面、准确监测的需求。分布式…...

C++(5)

1.运算符重载 头文件 #ifndef MYSTRING_H #define MYSTRING_H#include <iostream> #include <cstring>using namespace std;class myString { private:char *str;//C风格字符串int size0; public:std::string s_str;//转换构造函数myString(const std::string &a…...

【进程与线程】程序和进程在内存中的表现

在计算机系统中&#xff0c;程序和进程是两个密切相关但又有本质区别的概念&#xff0c;尤其在内存中的表现上有显著不同&#xff1a; 在这张图中可以直观地看出程序和进程在内存中的结构区别。 基本定义 程序 程序 是一个 静态实体&#xff0c;表示一组写好的指令和数据的…...

个人主页搭建全流程(Nginx部署+SSL配置+DCDN加速)

前言 最近开始准备秋招&#xff0c;打算做一个个人主页&#xff0c;以便在秋招市场上更有竞争力。 目前&#xff0c;现有的一些搭建主页的博文教程存在以下一些问题&#xff1a; 使用Github Page进行部署&#xff0c;这在国内访问容易受阻使用宝塔面板等框架&#xff0c;功能…...

语音合成的预训练模型

语音合成的预训练模型 与 ASR(语音识别)和音频分类任务相比,语音合成的预训练模型检查点明显较少。在 Hugging Hub 上,可以找到近 300 个适合的检查点。 在这些预训练模型中,重点关注两种在 Huggingface Transformers 库中开箱即用的架构——SpeechT5 和 Massive Multili…...

前端组件开发:组件开发 / 定义配置 / 配置驱动开发 / 爬虫配置 / 组件V2.0 / form表单 / table表单

一、最早的灵感 最早的灵感来自sprider / 网络爬虫 / 爬虫配置&#xff0c;在爬虫爬取网站文章时候&#xff0c;会输入给爬虫一个配置文件&#xff0c;里边的内容是一个json对象。里边包含了所有想要抓取的页面的信息。爬虫通过这个配置就可以抓取目标网站的数据。其实本文要引…...

Swagger生成Api文档的增强解决方案--knife4j

方法一&#xff1a; 使用步骤 1.导入 knife4j 的maven坐标 在pom.xml中添加依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.5.0</ver…...

Node.js - HTTP

1. HTTP请求 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是客户端和服务器之间通信的基础协议。HTTP 请求是由客户端&#xff08;通常是浏览器、手机应用或其他网络工具&#xff09;发送给服务器的消息&#xff0c;用来请求资源或执行…...

LangChain学习笔记2 Prompt 模板

安装 langchain 库 pip install langchain1、概念&#xff1a;提示和提示工程 在大语言模型&#xff08;LLMs&#xff09;时代&#xff0c;通过简单地更改提示中的指令&#xff0c;同一个模型可以执行多种任务。这一特性让 LLMs 在各类应用场景中都显得非常灵活和强大。然而&…...

如何在gitlab cicd中实现每月10号上午执行

在 GitLab CI/CD 中&#xff0c;可以通过设置定时触发器&#xff08;Schedules&#xff09;和脚本中的时间判断逻辑结合&#xff0c;确保任务只在每月 10 号的上午运行。 以下是实现的步骤&#xff1a; 1. 设置定时触发器 GitLab 提供了 Schedules 功能&#xff0c;可以指定每…...

SimpleFOC |SimpleFOC学习笔记汇总

在机器人领域&#xff0c;掌握无刷电机的控制相当于掌握机器人设计的“半壁江山”。这个年代&#xff0c;对个人来说学习一种新技术最好是通过开源项目了。通过开源项目快速将项目搭建起来&#xff0c;接着结合实践与理论才能真正掌握技术。 入门FOC&#xff0c;我认为最合适是…...

OpenArk64:Windows 系统分析与逆向工程工具详解

引言 在 Windows 系统的底层操作和逆向工程领域&#xff0c;OpenArk 是一款备受推崇的开源工具集。而 OpenArk64.exe 是 OpenArk 工具的 64 位版本&#xff0c;专门用于 64 位 Windows 系统。它提供了强大的功能&#xff0c;帮助用户深入分析系统内核、进程、文件、注册表等&a…...

数据储存与管理【大数据导论】

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;大数据入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…...

《从零到一:搭建高效体育直播网站的全流程技术指南》

搭建一个体育直播网站需要综合考虑技术架构、数据来源、用户体验、安全性等多个层面。从整体到细节&#xff0c;搭建这样一个网站的流程比较复杂&#xff0c;但可以分成几个重要的步骤和技术环节。以下是搭建体育直播网站的技术层面准备全流程&#xff1a; 一、需求分析与规划 …...

松散比较(PHP)(小迪网络安全笔记~

免责声明&#xff1a;本文章仅用于交流学习&#xff0c;因文章内容而产生的任何违法&未授权行为&#xff0c;与文章作者无关&#xff01;&#xff01;&#xff01; 附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;…...

一文了解如何使用 DBeaver 管理 DolphinDB

在日常的数据开发、分析和数据库运维中&#xff0c;一款优秀的 IDE 能够极大地提升工作效率。DBEaver 是一款由 Java 编写的一站式跨平台连接器&#xff0c;其社区版本已能支持连接近百种数据库&#xff0c;受到广大开发者的喜爱。近期。DolphinDB 与 DBeaver 团队共同努力&…...

网络基础知识指南|1-20个

1. IP地址: 即互联网协议地址&#xff0c;是用于标识互联网上的每一个设备或节点的唯一地址。IP地址的作用主要是进行网络设备的定位和路由&#xff0c;确保数据包可以从源设备准确地传送到目标设备。2. 子网掩码: 是用于将一个IP地址划分为网络地址和主机地址的工具。它通常与…...

01.09周四F34-Day50打卡

文章目录 1. -我大衣呢? -就在上次你放的地方。2. 这所学校是在曾经的影院上建立起来的。3. 她今天落到这个地步都怪你。4. 留得青山在,不怕没柴烧。(一息尚存,希望不灭。)5. 有善良的地方就有美德,有美德的地方就有奇迹。(《灰姑娘》原句)6. 为了和老外说话时不再发窘,所…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 &#xff08;FL&#xff09; 支持跨分布式客户端进行协作模型训练&#xff0c;而无需共享原始数据&#xff0c;这使其成为在互联和自动驾驶汽车 &#xff08;CAV&#xff09; 等领域保护隐私的机器学习的一种很有前途的方法。然而&#xff0c;最近的研究表明&…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...