发布时间:2026/8/5 21:43:41
设计模式 08 · 装饰器模式 上一篇代理模式结尾,我们埋了个钩子:代理和装饰器模式(Decorator)代码结构几乎一样,但意图相反——代理管控制,装饰器管增强。这一篇就来把装饰器讲透,顺便从另一头把这条界限夯实。装饰器要解决的问题,一句话:在不修改原对象、也不靠继承的前提下,动态地给对象叠加新功能。关键词是动态和叠加——你可以在运行时,像穿衣服一样,给一个对象一层层地包上新能力,想加哪层加哪层,顺序随意,还能自由组合。它最形象的比喻就是洋葱:核心是那个原始对象,外面一层层包裹着各种装饰,每一层都在前一层的基础上增强一点点。这一篇我们用订单价格计算这个再典型不过的场景切入:一个订单的最终价格,可能要在原价基础上叠加会员折扣、再叠加满减、再叠加优惠券……而且不同订单叠加的组合千变万化。你会看到,如果用继承来应对这种任意组合,会立刻陷入类爆炸的泥潭,而装饰器用组合优雅地化解了它——这正是第一篇合成复用原则(组合优于继承)最好的一个活教材。最后我们还会看 Java IO 流这个装饰器的教科书级应用,并把装饰器和代理彻底辨析清楚。这篇文章按这条线索展开:先看价格叠加用继承会怎样类爆炸;再引出装饰器,用洋葱式包裹优雅解决;然后讲清它的四个角色和骨架;接着剖析它为什么是组合优于继承的最佳范例;再看 Java IO 这个最经典的装饰器实例;最后从另一头辨析装饰器与代理的区别,给出适用边界。贯穿例是订单价格的优惠叠加。目录一个价格叠加引发的类爆炸装饰器模式:像洋葱一样层层包裹四个角色与骨架为什么装饰器是组合优于继承的活教材Java IO:装饰器的教科书级应用装饰器 vs 代理:从另一头把界限夯实什么时候用装饰器一、一个价格叠加引发的类爆炸我们的订单要算最终价格。基础是订单原价,但业务上有一堆可叠加的优惠:会员折扣(会员打 9 折)满减(满 100 减 20)优惠券(立减 10 元)麻烦在于,这些优惠是可以任意组合的:有的订单只用会员折扣,有的会员折扣 满减,有的三个全叠。如果用继承来表达这些组合,你得为每一种组合写一个子类:classOrder{doublegetPrice(){return100;}}classMemberOrderextendsOrder{...}// 只会员折扣classFullReductionOrderextendsOrder{...}// 只满减classCouponOrderextendsOrder{...}// 只优惠券classMemberFullReductionOrderextendsOrder{...}// 会员 满减classMemberCouponOrderextendsOrder{...}// 会员 优惠券classFullReductionCouponOrderextendsOrder{...}// 满减 优惠券classMemberFullReductionCouponOrderextendsOrder{...}// 三个全叠// ... 3 种优惠就已经 7 个组合类了看出灾难了吗?n 种优惠,组合起来就是 2ⁿ − 1 个子类。3 种就 7 个,再加一个限时折扣,立刻变成 15 个,以后每加一种优惠,类的数量翻倍式增长。这就是继承在多维度自由组合面前的死穴——继承是静态的、编译期定死的,它没法表达运行时任意搭配这件事。这正是第一篇讲合成复用原则时预告过的类爆炸。问题的本质是:每种优惠其实是一个独立的、可插拔的增强,它们本不该被继承关系锁死成一个个固定组合。我们需要的是——把每种优惠做成一个独立的装饰,谁想要哪几种,就在运行时把它们一层层套上去。这就是装饰器。二、装饰器模式:像洋葱一样层层包裹装饰器的核心思路:让装饰和被装饰的对象实现同一个接口,装饰持有一个被装饰对象的引用,在调用它的方法时,先(或后)加上自己的增强逻辑,再委托给内部对象。因为装饰本身也实现了同一接口,所以它又可以被下一层装饰继续包裹——层层嵌套,就像洋葱。先定义共同接口和原始对象:publicinterfaceOrder{doublegetPrice();// 算价格}// 原始对象:基础订单publicclassBasicOrderimplementsOrder{privatefinaldoubleprice;publicBasicOrder(doubleprice){this.priceprice;}publicdoublegetPrice(){returnprice;}}关键是一个抽象装饰器,它也实现Order,并持有一个Order:// 抽象装饰器:也是一个 Order,同时包着一个 OrderpublicabstractclassOrderDecoratorimplementsOrder{protectedfinalOrderorder;// 被装饰的对象publicOrderDecorator(Orderorder){this.orderorder;}}然后每种优惠是一个具体装饰器,各自只管自己那一点增强:// 会员折扣:在内部价格上打 9 折publicclassMemberDecoratorextendsOrderDecorator{publicMemberDecorator(Orderorder){super(order);}publicdoublegetPrice(){returnorder.getPrice()*0.9;// 先拿内部价,再叠加自己的逻辑}}// 满减:满 100 减 20publicclassFullReductionDecoratorextendsOrderDecorator{publicFullReductionDecorator(Orderorder){super(order);}publicdoublegetPrice(){doubleporder.getPrice();returnp100?p-20:p;}}// 优惠券:立减 10publicclassCouponDecoratorextendsOrderDecorator{publicCouponDecorator(Orderorder){super(order);}publicdoublegetPrice(){returnorder.getPrice()-10;}}用起来,就是把想要的优惠一层层套上去,像穿衣服:OrderordernewBasicOrder(100);// 原价 100ordernewMemberDecorator(order);// 套上会员折扣 → 90ordernewFullReductionDecorator(order);// 再套满减 → 90(不满100不减)ordernewCouponDecorator(order);// 再套优惠券 → 80System.out.println(order.getPrice());// 80对比第一节的类爆炸,升级点非常清晰:3 种优惠,现在只需要 3 个装饰器类,而不是 7 个组合类;n 种优惠是 n 个类,而不是 2ⁿ 个。而且组合是运行时决定的——想怎么叠就怎么叠,想换顺序就换顺序,全靠new的时候自由搭配,一个新组合都不用新增类。加一种限时折扣?加一个LimitTimeDecorator就行,已有代码一律不动。这就是装饰器的威力。三、四个角色与骨架把上面的代码抽象一下,装饰器模式有四个角色:角色本例中是谁职责抽象构件(Component)Order接口定义对象和装饰共同的接口具体构件(ConcreteComponent)BasicOrder被装饰的原始对象、核心抽象装饰器(Decorator)OrderDecorator实现 Component,持有一个 Component 引用具体装饰器(ConcreteDecorator)MemberDecorator等叠加具体的增强逻辑最关键的一点在抽象装饰器上:它既implements Component(所以它自己也是一个 Component,能被当作 Order 使用、也能被继续包裹),又持有一个 Component 引用(所以它能包住别人)。正是这既是又持有的双重身份,让装饰器能够无限层层嵌套。用一张洋葱图看最直观:这张图揭示了装饰器调用的精髓——调用像穿过洋葱一样由外向内层层深入,结果再由内向外层层加工返回。当你调用最外层CouponDecorator.getPrice()时:它要先知道内层价格,于是调FullReductionDecorator.getPrice(),后者又调MemberDecorator.getPrice(),再调到最核心的BasicOrder.getPrice()返回 100;然后 100 一层层往外走:会员折扣加工成 90、满减看不满 100 不动还是 90、优惠券减 10 变 80。每一层只关心在内层结果上加自己这一点,完全不知道整条链有多长、别的层是谁——这就是低耦合。四、为什么装饰器是组合优于继承的活教材第一篇讲合成复用原则时说过一句话:很多你以为需要继承的场景,其实用组合更好。装饰器就是这句话最完美的证明。我们把两种方案正面对一下账。继承方案(第一节)的问题:类爆炸:每个组合一个子类,2ⁿ 级增长;静态、编译期定死:一个订单一旦new成MemberOrder,它就永远是会员订单,运行时没法再给它加满减;组合关系写死在类里:想调整叠加顺序、动态增减优惠,统统做不到。装饰器方案(组合)的优势,正好逐条对上:类的数量是线性的:n 种增强 n 个装饰器,不随组合数膨胀;运行时动态组合:想叠哪几层、什么顺序,都在运行时用代码决定,极其灵活;符合开闭原则:加新增强 加新装饰器,不碰任何已有代码;每个装饰器职责单一:一个只管会员折扣、一个只管满减,清清爽爽(单一职责)。这里有个值得琢磨的细节:上面的OrderDecorator用了extends(继承)。这不矛盾吗,不是说组合优于继承吗?关键要看继承用在哪:装饰器用继承,只是为了让各个具体装饰器复用持有一个 Order 引用这个共同结构(一种代码复用),而它实现叠加功能这件核心的事,靠的是内部那个order引用——也就是组合。换句话说,装饰器的骨架是组合(持有并委托),继承只是个用来共享字段的辅助。真正让功能能层层叠加的,是组合,不是继承。这也提醒我们:“组合优于继承不是禁用继承”,而是用组合承担核心的能力扩展,继承退居为纯粹的代码复用。五、Java IO:装饰器的教科书级应用如果你觉得订单例子还不够有说服力,那 Java 标准库里有一个所有人天天用、却未必意识到是装饰器的经典——java.io 包。你一定写过这样的代码:// 一层层包起来,是不是和订单优惠叠加一模一样?InputStreaminnewBufferedInputStream(// 第三层:加缓冲newGZIPInputStream(// 第二层:加解压newFileInputStream(data.gz)));// 核心:从文件读这就是标准的装饰器!对照一下角色:抽象构件:InputStream(所有流的共同抽象);具体构件:FileInputStream(真正从文件读字节的核心);抽象装饰器:FilterInputStream(持有一个InputStream);具体装饰器:BufferedInputStream(加缓冲能力)、GZIPInputStream(加解压能力)、DataInputStream(加读取基本类型的能力)……Java IO 为什么要这么设计?因为数据来源(文件、网络、内存、数组)和处理能力(缓冲、加密、压缩、编码转换)是两个可以任意组合的维度。如果用继承,就会出现BufferedFileInputStream、GzipBufferedFileInputStream、BufferedNetworkInputStream……又一次 2ⁿ 类爆炸。而装饰器让你像搭积木一样自由组合:任何数据源,想加缓冲就包一层BufferedInputStream,想加解压就包一层GZIPInputStream,顺序、组合完全自由。理解了装饰器,你才算真正读懂了 java.io 那套套娃式的 API 为什么长这样。六、装饰器 vs 代理:从另一头把界限夯实上一篇从代理那头说过它俩结构相同、意图相反,这一篇我们从装饰器这头再夯一遍,让这条界限彻底清晰。先承认它们的共同点:都实现同一个接口、都持有一个同接口类型的对象、都在方法调用前后插入逻辑再委托给内部对象。光看代码,一个装饰器和一个代理确实可能长得几乎一样。区别在意图和用法上:代理 Proxy装饰器 Decorator意图控制访问(能不能访问、怎么访问)增强功能(叠加新能力)对内部对象的态度代理自己创建/管理真实对象,调用方常不知其存在被装饰对象从外部传入,调用方明确知道自己在包装数量通常一层(一个代理管一个真实对象)通常多层(为了叠加,层层嵌套是常态)关注点真实对象之外的事(日志、权限、事务、远程)真实对象本身能力的增强(缓冲、折扣、压缩)典型场景Spring AOP、RPC、权限校验Java IO、优惠叠加最好记的一条判据是这个:看内部对象是谁给的。装饰器:内部对象是你从外面new好、主动传进去的(new MemberDecorator(order))——你清楚地知道自己在一层层包装,目的是给它加料。层层叠加是装饰器的常态。代理:内部对象通常是代理自己内部持有/创建、你甚至看不到的(Spring 帮你生成代理,真实对象藏在里面)——你要的是访问它这件事被管控,而不是给它加料。只有一层、替你把关是代理的常态。一句话收束:你主动一层层往上包,是装饰器;它替你挡在前面把关,是代理。结构骗不了你,意图才是分水岭。七、什么时候用装饰器老规矩,泼冷水。装饰器很优雅,但它有代价:层层嵌套会产生很多小对象,而且调试时调用栈会变得很深——一个getPrice()要穿过好几层装饰器,排查问题时得一层层跟进去。所以它也不是万能的。适合用装饰器的信号:你需要给对象动态地、可选地、可叠加地添加功能,且这些功能能自由组合;用继承会导致类爆炸(多个独立维度的组合);希望增强逻辑能在运行时灵活搭配,而不是编译期写死。不必用装饰器的信号:功能组合是固定的、就那么一两种——那直接写死或用继承反而更简单,套装饰器是杀鸡用牛刀;你要的其实是控制访问而非增强能力——那是代理的活;增强只有一种、也不需要叠加——直接改或包一层普通方法就行。判断的核心还是那句贯穿全系列的话:先确认真的存在多个可自由组合的增强维度这个事实,装饰器才配得上它的嵌套复杂度。如果优惠就固定一种,硬套一套 Component/Decorator 的四角色结构,就是典型的过度设计。小结。装饰器模式让你在不改原对象、不靠继承的前提下,像洋葱一样给对象动态、可叠加地包裹新功能。它的核心是既是 Component 又持有 Component的双重身份,靠组合(持有并委托)实现层层嵌套,靠这一点优雅地干掉了继承在自由组合面前的 2ⁿ 类爆炸——是组合优于继承最好的活教材。Java IO 那套new BufferedInputStream(new GZIPInputStream(...))的套娃 API,就是它最经典的应用。记住它和代理的分水岭:你主动层层包裹去增强,是装饰器;它替你挡在前面做管控,是代理。下一篇我们讲适配器模式——当你手上的接口和你想用的接口对不上时(比如对接一个第三方物流 API),适配器就是那个转接头。

相关新闻

2026/8/5 21:43:41

设计模式 07 · 代理模式

从这一篇开始,我们离开"对象怎么造出来"(创建型),进入结构型模式——它们关心的是另一件事:已经造好的类和对象,该如何组合、连接、搭配,才能既满足需求又保持松耦合。 结构型七个模式,几乎都是在玩"包装"和"组合"的艺术。我们从其中实战出镜率最…

2026/8/5 21:43:41

Reia数据库架构揭秘:Turso如何支持离线与在线数据同步

Reia数据库架构揭秘:Turso如何支持离线与在线数据同步 【免费下载链接】Reia RPG game action-adventure MMO built with Godot and Rust. 项目地址: https://gitcode.com/gh_mirrors/re/Reia Reia作为一款基于Godot和Rust构建的动作冒险MMORPG游戏&#xff…

2026/8/5 21:38:41

深入解析信号量:从并发编程基石到生产者-消费者实战

1. 项目概述:从“抢车位”到“信号量”的认知跃迁如果你写过并发程序,或者调试过多线程的bug,那你一定对“竞态条件”这个词深恶痛绝。想象一个经典的场景:一个公共停车场,只有10个车位。如果没有管理,会发…

2026/8/6 2:34:33

Dockerfile打镜像突然报错mkdir都不行了

使用 WORKDIR 来替代 RUN mkdir -p 创建目录随后deployment找不到jar包位置了,改为绝对路径随后configmap也不生效了,改为挂整个目录# 挂载整个config目录,不要subPath,k8s会自动创建/apps/svr/config

2026/8/6 2:34:33

实战指南:如何高效使用Python盲水印技术保护数字版权

实战指南:如何高效使用Python盲水印技术保护数字版权 【免费下载链接】BlindWaterMark 盲水印 by python 项目地址: https://gitcode.com/gh_mirrors/bli/BlindWaterMark 在数字内容泛滥的时代,如何有效保护图片、视频等数字资产的版权成为了开发…

2026/8/6 2:34:33

电气系统稳定性分析:从平衡到模态的动态关联与工程应用

1. 从“平衡”到“模态”:电气系统分析的进阶视角在电气工程领域,尤其是涉及电机、变压器、电力电子设备乃至复杂电网系统的设计、运维与故障诊断时,我们常常听到两个核心概念:“平衡”与“模态”。乍一看,“电气平衡”…

2026/8/6 2:34:33

移动端本地AI渗透测试工具Nightcrawler:能力边界与实战评估指南

这类工具最值得先看的不是功能列表,而是它到底能不能在你的手机上稳定跑起来,以及它解决的“本地AI渗透测试”具体指什么。很多人看到“AI”和“渗透测试”就兴奋,但如果不搞清楚它的能力边界和运行条件,很容易在第一步就卡住。简…

2026/8/6 2:34:33

告别窗口遮挡烦恼:Windows窗口置顶神器AlwaysOnTop完全指南

告别窗口遮挡烦恼:Windows窗口置顶神器AlwaysOnTop完全指南 【免费下载链接】AlwaysOnTop Make a Windows application always run on top 项目地址: https://gitcode.com/gh_mirrors/al/AlwaysOnTop 你是否曾因重要窗口被其他程序遮挡而频繁切换&#xff1f…

2026/8/6 2:29:32

Hadoop伪分布式安装配置全流程详解与排错指南

1. 从零到一:为什么你的Hadoop安装总是不对劲? 搞大数据,Hadoop是绕不开的第一座山。但说实话,我见过太多人,包括一些工作两三年的朋友,在安装配置Hadoop这一步就栽了跟头。网上的教程五花八门&#xff0c…

2026/8/5 3:13:11

如何用免费工具突破游戏窗口限制:SRWE完整使用指南

如何用免费工具突破游戏窗口限制:SRWE完整使用指南 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE 你是否遇到过这样的困扰?想为心爱的游戏截图,却发现游戏不支持自定义分辨率…

2026/8/6 0:04:22

电力系统调度中的源荷不确定性建模与优化实践

1. 电力系统调度中的源荷不确定性挑战现代电力系统正面临前所未有的复杂性,其中源荷不确定性(Source-Load Uncertainty)已成为调度决策中最棘手的难题之一。我在参与某省级电网调度系统升级时,曾遇到风电预测误差导致日内调度计划…

2026/8/6 0:04:22

VGG-T3技术解析:3D重建速度的革命性突破

1. 项目概述:VGG-T3如何重新定义3D重建速度在计算机视觉领域,3D场景重建一直是个计算密集型任务。传统方法重建1000帧图像规模的场景往往需要数小时甚至更长时间,而英伟达最新发布的VGG-T3技术将这个时间压缩到了惊人的54秒。这个突破性进展来…

2026/8/6 0:04:22

深度解析旅游网站建设的意义及其对行业发展的深远影响与核心价值体现

在这个数字化浪潮席卷全球的今天,我们似乎已经忘记了,曾经有一段时间,人们想要去一个陌生的地方,只能靠在书桌前翻阅厚厚的旅游杂志,或者向刚从那里回来的朋友询问那些模糊不清的印象。那时候,“远方”是一个需要精打细算才能抵达的奢侈概念。而现在,只需要一部手机,轻…

2026/8/5 19:21:13

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/5 19:21:13

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/5 19:21:13

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…