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


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

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

相关文章

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…

K8S--配置存活、就绪和启动探针

目录 1 本人基础环境2 目的3 存活、就绪和启动探针介绍3.1 存活探针3.2 就绪探针3.3 启动探针 4 探针使用场景4.1 存活探针4.2 就绪探针4.3 启动探针 5 配置存活、就绪和启动探针5.1 定义存活探针5.2 定义一个存活态 HTTP 请求接口5.3 定义 TCP 的就绪探针、存活探测5.4 定义 g…

【HTML+CSS+JS+VUE】web前端教程-36-JavaScript简介

JavaScript介绍 JavaScript是一种轻量级的脚本语言&#xff0c;所谓脚本语言&#xff0c;指的是它不具备开发操作系统的能力&#xff0c;而是用来编写控制其他大型应用程序的“脚本” JavaScript是一种嵌入式语言&#xff0c;它本身提供的核心语法不算很多 为什么学习JavaScri…

LLM实现视频切片合成 前沿知识调研

1.相关产品 产品链接腾讯智影https://zenvideo.qq.com/可灵https://klingai.kuaishou.com/即梦https://jimeng.jianying.com/ai-tool/home/Runwayhttps://aitools.dedao.cn/ai/runwayml-com/Descripthttps://www.descript.com/?utm_sourceai-bot.cn/Opus Cliphttps://www.opu…

AI多模态论文解读:LLaVA-CoT:让视觉语言模型逐步推理

本文作者&#xff1a;AIGCmagic社区 猫先生 一、简 介 LLaVA-CoT引入了四个不同的阶段&#xff08;摘要、标题、推理和结论&#xff09;&#xff0c;使模型能够独立进行系统化的多阶段推理&#xff0c;显著提高了在推理密集型任务上的准确性。 编译了LLaVA-CoT-100k数据集&am…

分布式缓存redis

分布式缓存redis 1 redis单机&#xff08;单节点&#xff09;部署缺点 &#xff08;1&#xff09;数据丢失问题&#xff1a;redis是内存存储&#xff0c;服务重启可能会丢失数据 &#xff08;2&#xff09;并发能力问题&#xff1a;redis单节点&#xff08;单机&#xff09;部…

《C++11》nullptr介绍:从NULL说起

在C11之前&#xff0c;我们通常使用NULL来表示空指针。然而&#xff0c;NULL在C中有一些问题和限制&#xff0c;这就是C11引入nullptr的原因。本文将详细介绍nullptr的定义、用法和优点。 1. NULL的问题 在C中&#xff0c;NULL实际上是一个整数0&#xff0c;而不是一个真正的…

供应链数字化转型参考大型供应链系统技术架构设计方案

该文介绍了一个大型供应链系统技术架构的设计方案&#xff0c;包括整体设计、核心技术目录和应用案例。设计采用Choerodon微服务框架&#xff0c;关注海量并发、可伸缩性、安全性等方面。同时&#xff0c;方案符合大型企业结构的HR组织架构&#xff0c;支持多级组织架构和角色、…

STM32F1学习——DMA直接存储器存取

一、DMA直接存储器存取 DMA的全称是 Direct Memory Access 直接存储器存取&#xff0c;他可以提供外设和存储器间或存储器和存储器间的高速数据传输&#xff0c;无需CPU的干预。 STM32有12个DMA通道&#xff0c;由DMA1(7个通道组成)和DMA2(5个通道组成)&#xff0c;STM32F103C8…

一个使用 Golang 编写的新一代网络爬虫框架,支持JS动态内容爬取

大家好&#xff0c;今天给大家分享一个由ProjectDiscovery组织开发的开源“下一代爬虫框架”Katana&#xff0c;旨在提供高效、灵活且功能丰富的网络爬取体验&#xff0c;适用于各种自动化管道和数据收集任务。 项目介绍 Katana 是 ProjectDiscovery 精心打造的命令行界面&…

6.2 MySQL时间和日期函数

以前我们就用过now()函数来获得系统时间&#xff0c;用datediff()函数来计算日期相差的天数。我们在计算工龄的时候&#xff0c;让两个日期相减。那么其中的这个now函数返回的就是当前的系统日期和时间。 1. 获取系统时间函数 now()函数&#xff0c;返回的这个日期和时间的格…

用 Python 处理 CSV 和 Excel 文件

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

vulnhub靶场【IA系列】之Tornado

前言 靶机&#xff1a;IA-Tornado&#xff0c;IP地址为192.168.10.11 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.2 都采用虚拟机&#xff0c;网卡为桥接模式 本文所用靶场、kali镜像以及相关工具&#xff0c;我放置在网盘中&#xff0c;可以复制后面链接查看 htt…

[云讷科技] 用于软件验证的仿真环境

我们使用Pursuit自动驾驶仪为各种场景设计仿真环境&#xff0c;以便用户可以在模拟环境中直接验证他们的软件&#xff0c;无需现场测试。该环境基于Gazebo引擎。 1. 工作区目录 模拟环境的工作区位于提供的U盘中的~/pursuit_space/sitl_space_pursuit中。用户可以按照用户手册…

【Uniapp-Vue3】页面生命周期onLoad和onReady

一、onLoad函数 onLoad在页面载入时触发&#xff0c;多用于页面跳转时进行参数传递。 我们在跳转的时候传递参数name和age: 接受参数&#xff1a; import {onLoad} from "dcloudio/uni-app"; onLoad((e)>{...}) 二、onReady函数 页面生命周期函数中的onReady其…

iOS 解决两个tableView.嵌套滚动手势冲突

我们有这样一个场景&#xff0c;就是页面上有一个大的tableView&#xff0c; 每一个cell都是和屏幕一样高的&#xff0c;然后cell中还有一个可以 tableView&#xff0c;比如直播间的情形&#xff0c;这个时候如果我们拖动 cell里面的tableView滚动的话&#xff0c;如果滚动到内…

STM32 FreeRTOS移植

目录 FreeRTOS源码结构介绍 获取源码 1、 官网下载 2、 Github下载 源码结构介绍 源码整体结构 FreeRTOS文件夹结构 Source文件夹结构如下 portable文件夹结构 RVDS文件夹 MemMang文件夹 FreeRTOS在基于寄存器项目中移植步骤 目录添加源码文件 工程添加源码文件 …

【ASP.NET学习】Web Forms创建Web应用

文章目录 什么是 Web Forms&#xff1f;ASP.NET Web Forms - HTML 页面用 ASP.NET 编写的 Hello RUNOOB.COM它是如何工作的&#xff1f;经典 ASP ASP.NET Web Forms - 服务器控件经典 ASP 的局限性ASP.NET - 服务器控件ASP.NET - HTML 服务器控件ASP.NET - Web 服务器控件ASP.N…

Linux 常见运营维护,从安装软件开始,到mysql,php,redis,tomcat等软件安装,配置,优化,持续更新中。。。

下载centos7 CentOS 7 完整版&#xff08;DVD&#xff09;&#xff1a; https://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.isoCentOS 7 最小化版&#xff08;Minimal&#xff09;&#xff1a; https://mirrors.aliyun.com/centos/7/isos/x86_64/C…

用户界面软件05

已知应用 几乎所有的流行的用户界面架构都使用这种模式。我在这里举三个例子&#xff1a; 1. Seeheim 用户界面架构的特点是有一个应用核心的领域层和一个用户界面层。后者 被分为两层&#xff0c;叫做表示层和对话控制层。因为这个架构和面向事务系统有渊源&#xff0c;没有…