软件设计模式系列之十一——装饰模式

当谈到设计软件系统时,经常需要考虑如何使系统更加灵活、可扩展和易维护。设计模式是一种被广泛采用的方法,用于解决常见的设计问题,并提供了一套可重用的解决方案。装饰模式(Decorator Pattern)是一种结构型设计模式,它允许您在不改变对象接口的情况下动态地添加对象的功能或责任。在本文中,我们将深入探讨装饰模式,包括其定义、举例说明、结构、实现步骤、代码实现、典型应用场景、优缺点、类似模式以及最后的小结。

1 模式的定义

装饰模式属于结构型设计模式,它通过将对象包装在装饰器类中来动态地添加额外的行为,而不需要修改原始对象的代码。这个模式以透明的方式向对象添加功能,从而使您可以根据需要组合各种功能。

它的主要目的是动态地给对象添加额外的功能,同时又不需要修改对象的代码。这一模式通过将对象包装在装饰器类中来实现功能的扩展,而不是通过继承。因此,装饰模式被称为一种替代继承的模式,因为它提供了一种比继承更加灵活的方式来扩展对象的功能。

装饰模式的核心思想是将对象的行为拆分为多个可组合的部分,每个部分都可以独立地扩展。装饰模式的关键概念是使用组合而不是继承来扩展对象的功能,从而避免了继承可能引发的类爆炸问题,并提供了更加灵活的方式来定制对象的行为。这种方式可以有效地应对需求变化,使得代码更具可扩展性和可维护性。

2 举例说明

让我们通过几个简单的示例来说明装饰模式的概念。
咖啡店的例子,假设我们有一个咖啡店,我们有一种基本的咖啡(SimpleCoffee)和一些可选的装饰品,如牛奶(MilkDecorator)和糖(SugarDecorator)。我们希望客户能够根据他们的口味自由选择添加装饰品,而不需要为每种可能的组合创建新的类。

衣着搭配的例子,在日常生活中,我们经常需要根据不同的场合选择不同的服装搭配。例如,一件基本的衬衫可以通过添加领带、领结、围巾、外套等装饰品来改变外观,而不需要改变衬衫本身。
在这里插入图片描述

餐厅点菜的例子,在餐厅用餐时,您可以根据口味选择不同的菜肴,并根据个人喜好添加调味品,如辣椒酱、酱油、芥末等。这些调味品可以看作是对菜肴的装饰,使您的餐点更加符合口味。

汽车定制的例子,汽车制造商通常提供多种基本型号的汽车,然后允许客户根据自己的需求和喜好添加各种选项和装饰品,如皮革座椅、音响系统、太阳顶等,以创建定制的汽车。

这些例子都展示了在日常生活中如何使用装饰模式来动态地扩展对象的功能,而无需修改原始对象的代码。这种模式使得我们可以根据需要定制和个性化物品,从而增加了灵活性和选择性。

3 结构

装饰模式的结构包括以下关键组件:
在这里插入图片描述

Component(抽象组件):定义了一个抽象接口,用于被具体组件和装饰器实现。在上面的示例中,Coffee 接口就是抽象组件。

ConcreteComponent(具体组件):实现了抽象组件的接口,是我们想要扩展功能的具体对象。在示例中,SimpleCoffee 就是具体组件。

Decorator(装饰器):抽象装饰器类,实现了抽象组件的接口,并包含一个对抽象组件的引用。这个类可以有一个或多个具体的装饰器子类。在示例中,MilkDecorator 和 SugarDecorator 就是装饰器。

ConcreteDecorator(具体装饰器):具体装饰器类扩展了装饰器,并添加了具体的功能。它们通常会调用父类的方法以保留原始功能,然后添加自己的功能。在示例中,MilkDecorator 和 SugarDecorator 分别是具体装饰器。

4 实现步骤

要实现装饰模式,您可以按照以下步骤进行操作:

创建一个抽象组件(Component),它定义了装饰器和具体组件的共同接口。
创建具体组件(ConcreteComponent),它是被装饰的对象,并实现了抽象组件的接口。
创建一个抽象装饰器(Decorator),它也实现了抽象组件的接口,并包含一个对抽象组件的引用。
创建具体装饰器(ConcreteDecorator),它扩展了抽象装饰器,并添加了具体的功能。
在客户端中,通过组合不同的具体组件和装饰器来创建对象,并调用其方法。

5 代码实现

以下是一个使用Java代码实现咖啡店点餐的装饰模式示例:
在这里插入图片描述

首先,我们定义一个抽象的咖啡接口 Coffee:

public interface Coffee {double getCost();String getDescription();
}

然后,创建具体的咖啡类 SimpleCoffee,它实现了 Coffee 接口:

public class SimpleCoffee implements Coffee {@Overridepublic double getCost() {return 2.0;}@Overridepublic String getDescription() {return "Simple Coffee";}
}

接下来,创建装饰器抽象类 CoffeeDecorator,它也实现了 Coffee 接口,并包含一个对抽象组件的引用:

public abstract class CoffeeDecorator implements Coffee {private final Coffee decoratedCoffee;public CoffeeDecorator(Coffee decoratedCoffee) {this.decoratedCoffee = decoratedCoffee;}@Overridepublic double getCost() {return decoratedCoffee.getCost();}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}
}

现在,我们可以创建具体的装饰器类,比如 MilkDecorator 和 SugarDecorator:

public class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic double getCost() {return super.getCost() + 1.0;}@Overridepublic String getDescription() {return super.getDescription() + ", Milk";}
}public class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic double getCost() {return super.getCost() + 0.5;}@Overridepublic String getDescription() {return super.getDescription() + ", Sugar";}
}

现在,客户可以在咖啡店点餐并动态地添加装饰品:

public class CoffeeShop {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println("Cost: $" + coffee.getCost());System.out.println("Description: " + coffee.getDescription());// 添加牛奶和糖coffee = new MilkDecorator(coffee);coffee = new SugarDecorator(coffee);System.out.println("Cost: $" + coffee.getCost());System.out.println("Description: " + coffee.getDescription());}
}

这个示例演示了如何使用装饰模式来动态地扩展咖啡的功能,而不需要修改原始咖啡类。通过组合不同的装饰器,客户可以根据自己的口味点餐。

6 典型应用场景

装饰模式在许多情况下都有用,特别是当您需要在不修改现有代码的情况下扩展对象功能时。以下是一些典型的应用场景:
在这里插入图片描述

图形界面工具包:在GUI库中,装饰模式常用于添加额外的视觉效果,如边框、滚动条和工具提示,而无需修改原始组件的代码。

文件流处理:在文件处理中,您可以使用装饰模式来动态地添加压缩、加密、缓冲等功能,而不需要修改文件流的基本操作。

数据验证:在表单验证中,您可以使用装饰模式来添加各种验证规则,如必填字段、邮箱格式验证等,以提高代码的可维护性。

日志记录:装饰模式可以用于日志记录,使您能够动态地添加不同级别的日志信息,而不会影响原始业务逻辑。

7 优缺点

装饰模式具有以下优点和缺点:

优点:

  • 开闭原则。允许您添加新的装饰器类而不需要修改现有代码,遵守开闭原则(对扩展开放,对修改关闭)。

  • 灵活性。您可以根据需要组合不同的装饰器来创建复杂的对象,使系统更加灵活。

  • 单一职责原则。每个装饰器类都负责一个明确的功能,使得类的责任更加清晰。

  • 可重用性。由于装饰器可以独立使用,因此它们可以在不同的上下文中重复使用。

缺点:

  • 复杂性。如果使用不当,装饰器模式可能会导致类的层次结构变得复杂,使代码难以理解和维护。

  • 性能开销。每个装饰器都需要增加额外的开销,可能会影响性能,特别是在创建大量装饰对象时。

8 类似模式

有一些与装饰模式类似的设计模式,它们也关注于对象的功能扩展和组合,但在具体实现和应用上有一些不同。以下是一些与装饰模式相关的模式以及它们之间的联系。

适配器模式(Adapter Pattern):

适配器模式和装饰模式都属于结构型设计模式,它们都涉及到对象的包装。然而,它们的目的不同。适配器模式旨在兼容两个不同的接口,允许它们能够协同工作,而装饰模式旨在动态地添加功能,不改变原始接口。适配器模式涉及将一个接口转换成另一个接口,使得两者能够协同工作。装饰模式则是在不改变接口的前提下,动态地添加功能。在适配器模式中,适配器通常是一个新的类,而在装饰模式中,装饰器类与原始类共享相同的接口。

代理模式(Proxy Pattern):

代理模式和装饰模式都涉及到一个对象包装另一个对象。代理模式通常用于控制对对象的访问,例如,延迟加载、访问控制或监控。装饰模式用于动态地添加功能。代理模式的主要目的是控制访问,而装饰模式的主要目的是添加功能。代理通常在客户和真实对象之间充当中介,而装饰器是与真实对象共享相同接口的包装器。

组合模式(Composite Pattern):

组合模式和装饰模式都可以用于构建复杂的对象结构。它们都使用了递归组合对象,但目的不同。组合模式旨在创建树状结构,以表示部分-整体关系,并提供统一的方式来处理单个对象和组合对象。装饰模式用于动态地添加功能,通常是为了扩展单个对象的功能。

这些模式都与对象的功能扩展和组合有关,但它们的目的、用途和实现方式各不相同。装饰模式主要关注于动态添加功能而不改变接口,适配器模式关注于接口转换,代理模式关注于控制访问,组合模式关注于构建复杂的对象结构。在实际应用中,根据具体问题和需求,选择适合的设计模式是很重要的。

9 小结

装饰模式是一种强大的设计模式,它允许您在不修改现有代码的情况下动态地扩展对象的功能。通过定义抽象组件、具体组件、抽象装饰器和具体装饰器,您可以轻松地构建可维护和灵活的系统。然而,要小心不要过度使用装饰模式,以避免使代码变得复杂和难以理解。在适当的情况下,装饰模式可以成为您的设计工具箱中的强大工具,帮助您构建更加灵活和可扩展的软件系统。

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

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

相关文章

Vue路由与node.js环境搭建

目录 前言 一.Vue路由 1.什么是spa 1.1简介 1.2 spa的特点 1.3 spa的优势以及未来的挑战 2.路由的使用 2.1 导入JS依赖 2.2 定义两个组件 2.3 定义组件与路径对应关系 2.4 通过路由关系获取路由对象 2.5 将对象挂载到vue实例中 2.6 定义触发路由事件的按钮 2.7 定…

Python编辑器和Pycharm的傻瓜式安装部署

给我家憨憨写的python教程 有惊喜等你找噢 ——雁丘 Python解释器Pycharm的安装部署 关于本专栏一 Python编辑器1.1 使用命令提示符编写Python程序1.2 用记事本编写Python程序 二 Pycharm的安装三 Pycharm的部署四 Pycharm基础使用技巧4.1 修改主题颜色4.2 修改字体4.3 快速修…

nvme_queue_rq函数分析一

nvme I/O请求时,数据交互分析 主要函数为nvme_queue_rq: static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) {struct nvme_ns *ns hctx->queue->queuedata;struct nvme_queue *nvmeq hctx-…

js 事件流、事件冒泡、事件捕获、阻止事件的传播

事件流 js 事件的执行过程分为捕获阶段(由外层节点传播到内层节点)和冒泡阶段(由内层节点传播到外层节点),即先执行捕获阶段的代码,后执行冒泡阶段的代码 事件冒泡 js 事件中的代码默认在冒泡阶段执行&…

自动化发布npm包小记

1.注册npm账号 打开npm官网,并注册自己的npm账号 2.申请AccessToken 1.登录npm官网,登录成功后,点开右上角头像,并点击Access Tokens选项 2.点开Generate New Token下拉框,点击Classic Token(和Granular Access To…

VS2019创建GIt仓库时剔除文件或目录

假设本地有解决方案“SomeSolution” 1、首先”团队资源管理器“-“创建Git存储库”,选择“仅限本地”、“创建” VS会在解决方案目录下自动生成.gitattributes、.gitignore 2、编辑gitignore,直接拖到VS里或者用记事本打开。添加要剔除的文件或文件夹…

轻松自定义文件,悦享文件管理与格式转换!

大家好!厌倦了繁琐的文件命名和格式转换过程吗?现在,我们为您推出一款智能文件管理工具,让您能够轻松自定义文件改名,并将视频文件格式转换为MP3,让您的文件管理更加高效便捷! 首先&#xff0c…

Redis核心数据结构实战与高性能解析

目录 一、安装Redis 二、Redis线程与高性能 2.1 Redis是单线程么? 2.2 Redis读写是单线程为何这么快? 2.3 Redis如何处理并发操作命令? 三、核心数据结构实战 3.1 字符串常用操作实战 SET 存入键值对 SETNX SETEX MSET 批量存入键…

.Net IDE智能提示汉化(.Net6、AspNetCore)

先上现成的.net6汉化文件,可以手动下载后参照 如何为 .NET 安装本地化的 IntelliSense 文件 进行安装。或者使用后文的工具进行自动安装。 无对照英文在前中文在前 汉化内容来自 官方在线文档 ,某些内容可能存在明显的机翻痕迹。 上一些效果图&#x…

c语言基础知识+OS+数据结构

c语言: memory section: .bss: uninitialized or zero-initialized global and static variables .data: initialized global and static variables .text: Read only, code and const C语言编译流程: pre-compiler: …

快速安装和测试混淆后的IPA文件:使用Ipa Guard的签名和安装功能

​ 目录 转载:怎么保护苹果手机移动应用程序ipa中文件安全? 前言 1. 对敏感文件进行文件名称混淆 ​编辑 2. 更改文件的MD5值 3. 增加不可见水印处理 3. 对html,js,css等资源进行压缩 5. 删除可执行文件中的调试信息 转载&…

kubesphere中间件部署

微服务部署前中间件部署 一、MySQL部署 1.1 使用Docker实现MySQL主从复制 docker run -p 3307:3306 --name mysql-master \ -v /mydata/mysql/master/log:/var/log/mysql \ -v /mydata/mysql/master/data:/var/lib/mysql \ -v /mydata/mysql/master/conf:/etc/mysql \ -e My…

企业架构LNMP学习笔记59

目录介绍: bin:存放的是启动和关闭tomcat的脚本文件; conf:存放tomcat服务器的各种全局配置文件,其中最重要的是server.xml和web.xml lib: 存放的是tomcat服务器所需要的各种jar文件。java打包类库。 logs&#xff…

Vue项目前端代码防止被调试

项目背景 被安全测试针对了,总是调试我这不太安全的代码。前端代码深度混淆转成十六进制还不行,仍然找到加密方法,对后端数据进行解密。这次就修改了思路换种方法: 我承认阁下很强,但假如, 我是说假如打开控制台是空白页面&…

OSCP系列靶场-Esay-Gaara保姆级

OSCP系列靶场-Esay-Gaara 目录 OSCP系列靶场-Esay-Gaara总结准备工作信息收集-端口扫描目标开放端口收集目标端口对应服务探测 信息收集-端口测试22-SSH端口的信息收集22-SSH端口版本信息与MSF利用22-SSH协议支持的登录方式22-SSH弱口令爆破(待定)22-SSH手动登录尝试(无) 80-HT…

“三高”论文完美复现!基于PSO-VMD-MCKD方法的风机轴承微弱故障诊断,实现早期微弱故障诊断,MATLAB代码实现...

声明:对于作者的原创代码,禁止转售倒卖,违者必究! 本期文章思路来自振动测试与诊断期刊的一篇三高论文,点击链接可跳转。https://mp.weixin.qq.com/s/hmmDj5IwpaozeL4F0iI-2g 文章摘要如下: 针对风机滚动轴…

在qml中将一个16进制表示的颜色加上透明度

在qml中,我们在指定控件的颜色时,可以直接通过16进制的字符串来表示,比如"#ff0000"; 这种方式也比较符合UI设计人员的使用习惯。 但是假如要在此颜色的基础上,加个透明度的话,就要重新计算一番,比…

【iOS逆向与安全】插件开发之某音App直播间自动发666

1.目标 由于看直播的时候主播叫我发 666,支持他,我肯定支持他呀,就一直发,可是后来发现太浪费时间了,能不能做一个直播间自动发 666 呢?于是就花了几分钟做了一个。 2.操作环境 越狱iPhone一台 frida ma…

c++ 纯虚函数、抽象类

一、 纯虚函数 抽象类 只要有一个纯虚函数&#xff0c;这个类称为抽象类 抽象类的特点 1、无法实例化 2、抽象类的子类&#xff0c;必须要重写父类中的纯虚函数&#xff0c;否者也属于抽象类 例子一 #include <iostream> #include <string.h> using namespa…

tensorflow cuda gpu 安装

Windows 安装 CUDA/cuDNN 需要注意的是一定要选择 TensorFlow 和 CUDA相匹配&#xff0c;还需要查看下自己GPU的驱动版本&#xff0c;如果不匹配会出现很多问题。GPU驱动的版本可在 NVIDIA控制面板里找到&#xff1a; CUDA个版本与驱动的关系如下&#xff1a; GPU版本的 Tensor…