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

揭密设计模式:像搭乐高一样构建功能的装饰器模式

揭密设计模式:像搭乐高一样构建功能的装饰器模式

在软件开发中,我们常常会遇到一个问题:如何给一个对象动态地添加新功能,同时又不想修改它的代码?如果直接在原有类上修修补补,代码会变得臃肿复杂,难以维护。

今天,我们就来聊一个能完美解决这个问题的设计模式——装饰器模式 (Decorator Pattern)


什么是装饰器模式?

简单来说,装饰器模式允许你在不改变一个对象原有代码的前提下,为它增加新的职责或功能。 它就像是给一个基础对象“穿上”不同的“配件”,每件“配件”都代表一个新功能。


从一杯咖啡开始理解装饰器

为了更好地理解这个模式,我们来想象一下在咖啡店点咖啡的场景。

  1. 基础对象(Base Component) :首先,你有一杯最基础的黑咖啡(BlackCoffee) 。它有自己的价格(比如5元)和描述(“黑咖啡”)。
  2. 装饰器(Decorator) :现在你想给这杯咖啡加点东西,比如牛奶(Milk)和糖(Sugar) 。我们把这些配料看作是“装饰器”。它们本身也是咖啡的一种,只不过它们的作用是“包裹”另一杯咖啡,并在其基础上增加新的功能。

这个模式的关键在于,无论是黑咖啡还是牛奶、糖,它们都遵循同一个接口(Interface) 。这个接口定义了所有饮料都必须具备的行为,比如 getDescription()(获取描述)和 cost()(获取价格)。


装饰器模式的优势

  1. 符合开闭原则:我们不需要修改 BlackCoffee 类的代码。如果未来想增加新的配料,比如“奶油”,我们只需要新增一个 CreamDecorator 类即可,对原有代码完全无侵入。
  2. 避免“子类爆炸” :如果不用装饰器模式,我们可能需要创建 MilkCoffeeSugarCoffeeMilkSugarCoffee 等大量子类来应对不同的组合。这会让代码变得异常复杂。装饰器模式通过组合而非继承的方式,巧妙地解决了这个问题。
  3. 动态组合功能:用户可以根据需要,在运行时自由组合功能,比如先加奶再加糖,或者只加糖,非常灵活。

UML 类图

为了更直观地理解,我们来看一下装饰器模式的 UML 类图。

在这里插入图片描述

  • Beverage:定义了所有对象都必须实现的接口。
  • BlackCoffee:具体组件,提供了最基础的功能。
  • BeverageDecorator:抽象装饰器,继承了接口并持有一个接口的引用。
  • MilkDecoratorSugarDecorator:具体的装饰器,用来添加新功能。

装饰器模式在框架中的应用

除了我们自己手写的代码,装饰器模式在许多成熟的框架中都有广泛应用。

Java I/O 流

Java 的 I/O 流库是装饰器模式最经典的例子之一。InputStreamOutputStream 是最基础的接口。而像 BufferedInputStreamDataInputStreamGZIPInputStream 等类,都是装饰器。它们通过包裹一个基础的 InputStream,为其添加新的功能,比如提供缓冲、处理基本数据类型、文件解压缩等。

Spring 框架

在 Spring 框架中,装饰器模式与代理模式的思想常常结合使用。当一个 Bean 被 Spring AOP 增强时,Spring 会创建一个代理对象。这个代理对象实际上就是装饰器,它包裹着原始的 Bean 对象。当方法被调用时,代理对象会先执行一些额外的逻辑(比如事务的开启和提交、日志的记录),然后再将调用转发给原始的 Bean 对象。这种设计使得 Spring 可以在不修改原始业务代码的情况下,为其动态地添加横切关注点(Cross-cutting Concerns),完美体现了装饰器模式的精髓。


装饰器模式与相似模式的对比

最后,为了更精确地理解装饰器模式,我们来把它和两个容易混淆的模式进行比较。

装饰器模式 vs 代理模式
  • 目的不同:装饰器模式的目的是动态地增强一个对象的功能。而代理模式的目的是控制对一个对象的访问
  • 联系与区别:虽然两者在结构上相似(都持有一个对目标对象的引用),但其意图不同。在某些情况下(如 Spring AOP),代理既可以作为访问控制的手段,也可以作为功能增强的方式,从而模糊了两者之间的界限。
装饰器模式 vs 组合模式
  • 目的不同:装饰器模式是为了增强一个单一对象的功能。而组合模式是为了将对象组织成树形结构,以表示“部分-整体”的层次关系。
  • 结构不同:在装饰器模式中,装饰器和被装饰者都实现同一个接口。而在组合模式中,组合对象和叶子对象也实现同一个接口,但组合对象内部持有的是多个接口的引用(一个集合),目的是管理子对象。

Java 代码实现

接下来,我们用 Java 来实现这个咖啡店的例子。

1. 统一接口:Beverage

首先,定义所有饮料都必须实现的接口。

// Beverage.java
public interface Beverage {String getDescription();double cost();
}
2. 具体组件:BlackCoffee

然后,我们创建最基础的饮料类,它实现了 Beverage 接口。

// BlackCoffee.java
public class BlackCoffee implements Beverage {@Overridepublic String getDescription() {return "黑咖啡";}@Overridepublic double cost() {return 5.0;}
}
3. 抽象装饰器:BeverageDecorator

为了让所有装饰器都具有统一的结构,我们创建一个抽象装饰器类。它也实现了 Beverage 接口,并持有一个对 Beverage 对象的引用。所有的具体装饰器都将继承这个抽象类。

// BeverageDecorator.java
public abstract class BeverageDecorator implements Beverage {protected Beverage beverage;public BeverageDecorator(Beverage beverage) {this.beverage = beverage;}@Overridepublic abstract String getDescription();@Overridepublic abstract double cost();
}
4. 具体装饰器:MilkDecorator 和 SugarDecorator

现在,我们创建具体的装饰器类。它们继承 BeverageDecorator 并重写方法,在原有功能上添加新的职责。

// MilkDecorator.java
public class MilkDecorator extends BeverageDecorator {public MilkDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {// 在原有描述上添加 "加奶"return beverage.getDescription() + ",加奶";}@Overridepublic double cost() {// 在原有价格上加上牛奶的费用return beverage.cost() + 3.0;}
}
// SugarDecorator.java
public class SugarDecorator extends BeverageDecorator {public SugarDecorator(Beverage beverage) {super(beverage);}@Overridepublic String getDescription() {// 在原有描述上添加 "加糖"return beverage.getDescription() + ",加糖";}@Overridepublic double cost() {// 在原有价格上加上糖的费用return beverage.cost() + 1.0;}
}
5. 客户端代码:如何使用

最后,我们来看看如何将这些组件组合起来,构造出我们想要的咖啡。

// Main.java
public class Main {public static void main(String[] args) {// 1. 来一杯纯黑咖啡Beverage blackCoffee = new BlackCoffee();System.out.println("描述: " + blackCoffee.getDescription() + ",价格: " + blackCoffee.cost());// 2. 来一杯加奶的黑咖啡Beverage milkCoffee = new MilkDecorator(blackCoffee);System.out.println("描述: " + milkCoffee.getDescription() + ",价格: " + milkCoffee.cost());// 3. 来一杯加奶又加糖的黑咖啡Beverage milkSugarCoffee = new SugarDecorator(milkCoffee);System.out.println("描述: " + milkSugarCoffee.getDescription() + ",价格: " + milkSugarCoffee.cost());}
}

运行结果:

描述: 黑咖啡,价格: 5.0
描述: 黑咖啡,加奶,价格: 8.0
描述: 黑咖啡,加奶,加糖,价格: 9.0

结语

装饰器模式是一种强大且灵活的设计模式。它通过“包裹”而非修改的方式,让我们可以像搭乐高积木一样,动态地为对象添加和组合功能。当你的系统需要灵活扩展、避免大量子类时,不妨考虑一下这个精妙的模式。


文章转载自:

http://ldV3j4qs.xjqkh.cn
http://J2fQwNF2.xjqkh.cn
http://uSw7NR8N.xjqkh.cn
http://lfe6anfX.xjqkh.cn
http://UgZ9YF22.xjqkh.cn
http://wC2A628H.xjqkh.cn
http://HCXBfpHf.xjqkh.cn
http://HEWSVKXc.xjqkh.cn
http://b5Wy8T6G.xjqkh.cn
http://1hoqdjN6.xjqkh.cn
http://f9qNprDx.xjqkh.cn
http://SbI8dbj2.xjqkh.cn
http://W1x0KvGK.xjqkh.cn
http://Su5B9BCE.xjqkh.cn
http://llm5Qwhh.xjqkh.cn
http://QjuGwmyU.xjqkh.cn
http://3HmjmNUg.xjqkh.cn
http://GD87P0n8.xjqkh.cn
http://V9uwsH0D.xjqkh.cn
http://iUPM2pPC.xjqkh.cn
http://GNohpYW9.xjqkh.cn
http://es7C6Fkq.xjqkh.cn
http://E1RsIPtK.xjqkh.cn
http://lxzYzHFB.xjqkh.cn
http://8P6G7U0O.xjqkh.cn
http://nEchqim7.xjqkh.cn
http://kS9sR8QQ.xjqkh.cn
http://QQUB0IAZ.xjqkh.cn
http://taFxm9Op.xjqkh.cn
http://UE3LbCxc.xjqkh.cn
http://www.dtcms.com/a/363348.html

相关文章:

  • 《Vue进阶教程》(7)响应式系统介绍
  • 05 Centos 7尝试是否有网络
  • 基于STM32与华为云联动的智能电动车充电桩管理系统
  • Stop-Process : 由于以下错误而无法停止进程“redis-server (26392)”: 拒绝访问。
  • Python OpenCV图像处理与深度学习:Python OpenCV DNN模块深度学习与图像处理
  • PHP的error_log()函数
  • 智慧工地如何撕掉“高危低效”标签?三大社会效益重构建筑业价值坐标
  • 一款开源的CMS系统简介
  • 优秀开源内容转自公众号后端开发成长指南
  • QuickUp-Ubuntu
  • js设计模式-职责链模式
  • 【音视频】Opus 编码格式介绍
  • WPF应用程序资源和样式的使用示例
  • HarmonyOS 应用开发新范式:深入剖析 Stage 模型与 ArkUI 最佳实践
  • 基于vue3和springboot框架集成websocket
  • 网络数据包是怎么在客户端和服务端之间进行传输的?
  • C#实现与西门子S7-1200_1500 PLC通信
  • qt QWebSocket详解
  • 系统扩展策略
  • 【LeetCode_26】删除有序数组中的重复项
  • 小迪web自用笔记24
  • GPT-5论文选题实测:如何从2000篇文献中提炼出3个可快速落地的高命中选题?
  • 从零开始学Vue3:Vue3的生命周期
  • Leetcode二分查找(4)
  • 开悟篇Docker从零到实战一篇文章搞定
  • 洗衣店小程序的设计与实现
  • GDB 调试
  • 深度学习篇---DenseNet网络结构
  • Spring Boot手写10万敏感词检查程序
  • C#----异步编程