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

一天一个设计模式——装饰器模式

装饰器模式(Decorator Pattern)

装饰器模式是一种结构型设计模式,它允许在不修改原有对象结构的情况下,通过动态地“包装”对象来为其添加新的行为或功能。这种模式类似于“层层包裹”,每个包装层(装饰器)可以增强或修改被装饰的对象,而不影响其他同类对象。

为什么使用装饰器模式?
  • 问题解决:当你需要为一个类添加新功能时,如果直接修改类,会违反开闭原则(对扩展开放,对修改关闭)。继承虽然可行,但会导致类爆炸(太多子类)。
  • 适用场景
    • 需要动态添加或移除功能。
    • 需要在运行时组合多个功能。
    • 例如:UI组件的边框、滚动条添加;IO流的包装(如BufferedInputStream)。
  • 优点
    • 灵活性高,支持组合。
    • 符合开闭原则。
  • 缺点
    • 可能产生过多小对象,增加系统复杂性。
    • 调试时调试链较难。
核心结构

装饰器模式涉及四个主要角色:

  • Component(组件):抽象组件,定义接口。
  • ConcreteComponent(具体组件):实现基本功能的对象。
  • Decorator(装饰器):持有Component引用,提供装饰接口。
  • ConcreteDecorator(具体装饰器):实现具体的装饰逻辑,通常调用Component的方法并添加额外行为。

UML类图描述(文本表示):

Component (抽象)↑
ConcreteComponent (具体组件)↑
Decorator (装饰器) ──> Component↑
ConcreteDecoratorA/B (具体装饰器)
经典示例:咖啡店点单系统

假设我们有一个咖啡店系统,基本咖啡有价格,装饰器可以添加牛奶、糖等配料动态计算总价。

装饰器模式 Java 实现示例

以下是装饰器模式在 Java 中的经典实现,使用咖啡店点单系统作为示例。结构与 Python 版本类似:定义接口、具体组件、抽象装饰器和具体装饰器。通过动态包装,可以灵活添加配料并计算总价。

核心代码
// 1. 组件接口
interface Coffee {double getCost();String getDescription();
}// 2. 具体组件
class SimpleCoffee implements Coffee {@Overridepublic double getCost() {return 5.0;}@Overridepublic String getDescription() {return "Simple Coffee";}
}// 3. 抽象装饰器
abstract class CoffeeDecorator implements Coffee {protected Coffee coffee;public CoffeeDecorator(Coffee coffee) {this.coffee = coffee;}@Overridepublic double getCost() {return coffee.getCost();}@Overridepublic String getDescription() {return coffee.getDescription();}
}// 4. 具体装饰器:牛奶
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic double getCost() {return super.getCost() + 2.0;}@Overridepublic String getDescription() {return super.getDescription() + ", Milk";}
}// 5. 具体装饰器:糖
class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic double getCost() {return super.getCost() + 1.0;}@Overridepublic String getDescription() {return super.getDescription() + ", Sugar";}
}// 使用示例
public class DecoratorDemo {public static void main(String[] args) {// 基本咖啡Coffee coffee = new SimpleCoffee();System.out.println(coffee.getDescription() + ": $" + coffee.getCost());// 添加牛奶Coffee coffeeWithMilk = new MilkDecorator(coffee);System.out.println(coffeeWithMilk.getDescription() + ": $" + coffeeWithMilk.getCost());// 添加牛奶和糖Coffee coffeeWithMilkSugar = new SugarDecorator(coffeeWithMilk);System.out.println(coffeeWithMilkSugar.getDescription() + ": $" + coffeeWithMilkSugar.getCost());}
}
运行输出
Simple Coffee: $5.0
Simple Coffee, Milk: $7.0
Simple Coffee, Milk, Sugar: $8.0

这个 Java 实现利用接口和继承,展示了装饰器的透明性:装饰后的对象仍可作为 Coffee 类型使用。如果需要编译运行或其他变体(如泛型支持),可以进一步调整!
``

这个例子展示了如何通过层层装饰动态构建对象,而无需创建大量子类。

与其他模式的比较
模式区别与联系
适配器适配器转换接口,装饰器增强功能。
代理代理控制访问,装饰器添加行为。
组合组合处理树状结构,装饰器是线性包装。

装饰器模式的优势展示:通过对比与实际场景

咖啡店的简单示例更像是入门演示,它展示了基本结构,但确实没突出装饰器模式的“杀手级”优势——动态组合、避免类爆炸、符合开闭原则。下面我来通过对比继承方式一个更真实的Java IO流示例,来直观展示这些优势。继承方式会让你看到“噩梦”:如果有N种基础对象和M种装饰,每种组合都需要一个子类,导致2^(N*M)级的类爆炸!装饰器只需O(N+M)个类,就能无限组合。

1. 对比:继承 vs 装饰器(以咖啡店扩展为例)

假设基础咖啡有3种(Espresso、Latte、Mocha),装饰有4种(Milk、Sugar、Whip、Soy)。如果用继承

  • 需要为每种基础+每种装饰组合创建子类:3 * 2^4 = 48个子类!(二进制组合,每种装饰可选)。
  • 代码维护地狱:新增一种装饰(如Chocolate),需改48个类,违反开闭原则。

装饰器:只需3个基础类 + 1个抽象装饰器 + 4个具体装饰器 = 8个类。运行时动态包装,就能生成任意组合(如Latte + Milk + Whip)。

伪代码对比(Java风格)

  • 继承方式(简化,只展示部分,实际会爆炸)
    class Espresso {}  // 基础
    class EspressoWithMilk extends Espresso {}  // +Milk
    class EspressoWithMilkAndSugar extends EspressoWithMilk {}  // +Sugar
    // ... 继续下去,48个类...
    // 新增Chocolate?重写所有48个!
    
  • 装饰器方式(之前示例扩展)
    // 只需新增一个ChocolateDecorator
    class ChocolateDecorator extends CoffeeDecorator {public ChocolateDecorator(Coffee coffee) { super(coffee); }@Override public double getCost() { return super.getCost() + 1.5; }@Override public String getDescription() { return super.getDescription() + ", Chocolate"; }
    }// 使用:动态组合,无需新类
    Coffee fancy = new ChocolateDecorator(new WhipDecorator(new MilkDecorator(new Latte())));
    // 输出: Latte, Milk, Whip, Chocolate: $12.5
    

优势一览:

  • 灵活性:运行时决定组合(如用户点单时加Chocolate),无需编译新类。
  • 可维护:新增装饰只需1个类,不碰原有代码。
  • 内存高效:共享基础对象,避免重复实现。
2. 真实场景:Java IO流中的装饰器(BufferedInputStream等)

Java标准库就是装饰器模式的教科书!InputStream是抽象组件,FileInputStream是具体组件,BufferedInputStreamDataInputStream等是装饰器。优势:无需为“文件+缓冲+数据”创建专用类,直接层层包装。

为什么突出优势?

  • 问题:读文件时,可能需要缓冲(加速)、加密(安全)、压缩(节省)。继承会产生海量子类(如FileBufferedEncryptedStream…)。
  • 装饰器解决:动态包装,组合任意功能,且透明(包装后仍用InputStream接口)。
  • 实际收益:Java IO库只需几十个类,就能支持无限IO变体;性能高(缓冲减少系统调用)。

Java代码实现(简化版,模拟文件读)

import java.io.*;// 1. 组件接口(Java已有)
abstract class InputStream {  // 简化,实际是java.io.InputStreampublic abstract int read() throws IOException;
}// 2. 具体组件:文件流
class FileInputStream extends InputStream {private String fileContent = "Hello, World!";  // 模拟文件内容private int index = 0;@Overridepublic int read() throws IOException {if (index < fileContent.length()) {return fileContent.charAt(index++);}return -1;  // EOF}
}// 3. 抽象装饰器
abstract class FilterInputStream extends InputStream {protected InputStream in;public FilterInputStream(InputStream in) {this.in = in;}@Overridepublic int read() throws IOException {return in.read();}
}// 4. 具体装饰器:缓冲(加速读)
class BufferedInputStream extends FilterInputStream {private byte[] buf = new byte[1024];private int count = 0, pos = 0;public BufferedInputStream(InputStream in) {super(in);}@Overridepublic int read() throws IOException {if (pos >= count) {count = in.read(buf);  // 批量读pos = 0;if (count == -1) return -1;}return buf[pos++] & 0xFF;  // 模拟缓冲优势}
}// 5. 另一个具体装饰器:数据流(读int等结构化数据)
class DataInputStream extends FilterInputStream {public DataInputStream(InputStream in) {super(in);}public int readInt() throws IOException {  // 添加新方法:读intint b1 = read(), b2 = read(), b3 = read(), b4 = read();if ((b1 | b2 | b3 | b4) < 0) throw new IOException("EOF");return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;}
}// 使用示例(突出动态组合)
public class IODecoratorDemo {public static void main(String[] args) throws IOException {// 基础:直接读文件(慢,无缓冲)InputStream file = new FileInputStream();System.out.println("基础读: " + (char) file.read());  // H (每次系统调用,慢)// 组合1:加缓冲(加速)InputStream buffered = new BufferedInputStream(file);System.out.println("缓冲读: " + (char) buffered.read());  // H (批量读,快)// 组合2:缓冲 + 数据流(加速 + 结构化读)InputStream dataBuffered = new DataInputStream(new BufferedInputStream(file));// 假设文件有int数据,读int而非字节// int value = ((DataInputStream) dataBuffered).readInt();  // 新功能!// 优势:新增加密装饰器?只需1类,包装dataBuffered即可。// 无需改FileInputStream!}
}

运行洞察(手动模拟输出):

  • 基础读:每次read()都“系统调用”,文件大时超慢。
  • 缓冲后:批量读,I/O调用减99%,性能飞起。
  • 加数据流:还能读int/long等,无需改底层文件流。

这个IO示例展示了实际工程优势:在高性能系统(如服务器日志读)中,装饰器让代码简洁、可扩展。如果不用,它会是继承的“类工厂”灾难。

怎么判断什么时候使用装饰器模式?

装饰器模式的核心价值在于动态扩展对象行为,而非静态继承。它适合那些“可选、组合、可运行时调整”的场景。下面我从几个实用维度帮你判断:通过检查你的问题是否匹配这些条件,就能决定是否引入装饰器。如果是“静态、固定、少量变体”的需求,可能继承或直接修改更简单。

主要判断标准

以下是基于设计模式经典指南(如GoF书)和实践经验的判断点。我用表格总结,便于对比(每个点后附引用来源):

判断标准详细说明示例场景
需要运行时动态添加/移除功能如果功能不是编译时决定的,而是运行时根据条件(如用户输入)组合(如加“牛奶”或“糖”),装饰器能层层包装,而不需预定义所有组合。Java IO流:基础文件流 + 运行时加缓冲(BufferedInputStream)或加密。
避免类爆炸(继承过多子类)如果基础类有N种,装饰有M种,继承需创建N×2^M个子类;装饰器只需N+M个类,就能无限组合。咖啡店:3种咖啡 + 4种配料,用继承=48类;装饰器=7类搞定。
符合开闭原则(扩展不改原代码)新功能只需加新装饰器类,不碰原有组件代码。UI组件:基础按钮 + 运行时加边框/滚动条,不改按钮类。
遵守单一职责原则每个类只管一件事,装饰器将独立行为(如“加日志”)分离成小类,便于复用。日志系统:核心业务 + 装饰加文件/数据库日志,各司其职。
继承不可用或不合适当基类是第三方库(不能继承)或继承会导致紧耦合时,用装饰器包装。第三方API:包装响应加缓存,而不继承其类。
快速决策流程
  1. 问自己:功能是“可选组合”还是“必选固定”?如果是前者(如插件系统),用装饰器。
  2. 检查复杂度:变体>5种?继承会爆炸→装饰器。
  3. 测试可行性:如果包装后对象仍需透明使用(如统一接口),完美匹配。
  4. 反例(何时不用):变体少、静态绑定(如简单if-else),用继承或策略模式更轻量。

实际中,Java IO库就是最佳证明:它用装饰器处理无数IO变体,避免了“继承地狱”。

http://www.dtcms.com/a/482737.html

相关文章:

  • 婚恋交友 APP 核心功能分析:从匹配逻辑到用户体验的全链路设计
  • 用一个 prompt 搭建带 React 界面的 Java 桌面应用
  • 宁波建网站价格wordpress注明网站
  • wordpress添加网站地图黑龙江新闻头条最新消息
  • 机械臂装配自动化推动紧固件设计革新
  • JSAR 入门教程:从零开始开发空间天气小摆件
  • 【Pytorch】什么是梯度
  • 核间通信机制
  • 吕口*云蛇激光*VS*薄利魔刀*武打算法的方案
  • CSP-S模拟赛五总结(实际难度远低于提高组)
  • 网站建设服务器的配置wordpress 输出sql
  • 邵阳建设网站公司app在线生成平台 免费
  • 如何在第三方网站做推广什么关键词可以搜到那种
  • 深度解析 PostgreSQL 中的 ctid、xmin、xmax:从原理到实战
  • 2-sat
  • KPI、OKR 和 GS 的区别
  • 坂田网站建设费用明细wordpress 最近登录地址
  • 网站开发技术微信公众平台如何绑定网站
  • electron+react+esbuild入门项目
  • iOS 应用加固与苹果软件混淆指南,如何防止 IPA 被反编译与二次打包?
  • jsp电商网站怎么做网络营销是什么部门
  • 网站优化体验报告百度网盟推广步骤
  • 物联网系统三层架构解析
  • 京东联手广汽、宁德时代造车!
  • PEFT适配器加载
  • React Hooks 核心规则自定义 Hooks
  • 江门网站制作 华企立方洛宁县东宋乡城乡建设局网站
  • 河南网站建设哪家有三品合一网站建设案例
  • 位运算专题总结:从变量初始化陷阱到理解异或分组
  • Linux学习笔记(八)--环境变量与进程地址空间