系列定位软考软件设计师 / 系统架构设计师 — 结构型模式专题第 3 讲考察分值上午题 1-2 分下午题常作为代码填空或类图识别出现难度等级⭐⭐⭐☆☆结构像套娃层层包装掌握递归组合后很稳一、考纲定位与模式定义1.1 考纲要求装饰模式在软考中属于结构型模式的基础内容。考察形式包括上午选择题判断场景描述所属模式识别装饰模式与桥接模式、适配器模式的区别判断类图中是否存在 递归组合 结构Decorator 组合 Component下午设计题补全 Decorator 抽象类的构造器注入 Component和委托逻辑补全 ConcreteDecorator 中新增的职责方法识别类图中 Component、ConcreteComponent、Decorator、ConcreteDecorator 四个角色1.2 模式定义装饰模式动态地给一个对象添加一些额外的职责就增加功能来说装饰模式比生成子类更为灵活。核心意图当需要在不改变原有对象接口的前提下给对象动态添加新功能时不用继承继承是静态的、编译时确定的而是用包装Wrapper的方式在运行时层层套娃。每一层装饰者都持有被装饰对象的引用在调用被装饰对象方法的前后插入自己的增强逻辑。通俗理解你去奶茶店点一杯奶茶。基础款是 珍珠奶茶ConcreteComponent你可以加料 椰果ConcreteDecoratorA、 布丁ConcreteDecoratorB、 奶盖ConcreteDecoratorC。每加一种料都是在原有奶茶的基础上包装一层最终你拿到的是一杯 奶盖 布丁 椰果 珍珠奶茶。每一层都不改变奶茶的 喝 这个接口但增加了新的口感职责。二、UML 类图与角色划分┌────────────────────────────────────────────────────────────┐ │ Client │ │ main() │ │ ├── Component c new ConcreteComponent(); │ │ ├── c new ConcreteDecoratorA(c); // 第一层包装 │ │ ├── c new ConcreteDecoratorB(c); // 第二层包装 │ │ └── c.operation(); // 逐层调用最终到 ConcreteComponent │ └─────────────────────────────┬──────────────────────────────┘ │ uses ▼ ┌────────────────────────────────────────────────────────────┐ │ interface │ │ Component │ │ operation() │ └─────────────────────────────┬──────────────────────────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ ConcreteComponent │ │ Decorator │ │ (被装饰的原始对象) │ │ - component: │ │ operation() │ │ Component │ │ (原始业务逻辑) │ │ operation() { │ │ │ │ // 可前置增强 │ │ │ │ component. │ │ │ │ operation(); │ │ │ │ // 可后置增强 │ │ │ │ } │ └────────────────────────┘ └──────────┬─────────────┘ │ ┌──────────────────┴──────────────────┐ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ ConcreteDecoratorA │ │ ConcreteDecoratorB │ │ operation() { │ │ operation() { │ │ // 增强A │ │ // 增强B │ │ super.op(); │ │ super.op(); │ │ // 增强A │ │ // 增强B │ │ } │ │ } │ │ addedBehavior() │ │ addedBehavior() │ └─────────────────────┘ └─────────────────────┘角色职责软考填空关键词Component抽象组件定义一个对象接口规范所有对象被装饰者和装饰者的统一接口interface/abstract classoperation()ConcreteComponent具体组件被装饰的原始对象实现 Component 接口完成核心功能原始业务类如Coffee、FileReaderDecorator抽象装饰者实现 Component 接口组合一个 Component 引用将请求委托给被装饰对象abstract classprotected Component component 构造器注入ConcreteDecorator具体装饰者继承 Decorator在operation()中调用父类委托方法的前后添加自己的增强逻辑extends Decoratorsuper.operation() 新增职责方法Client客户端创建 ConcreteComponent并用 ConcreteDecorator 层层包装最终调用operation()new DecoratorA(new DecoratorB(new Component()))类图识别要点软考类图中装饰模式的核心特征是Decorator 类与 Component 是组合关系Decorator 持有 Component 引用且 Decorator 与 Component 实现同一个接口或继承同一个抽象类。如果类图里只有一个转换类连接两个不同接口 → 适配器如果有两个独立继承树 → 桥接如果是一个递归包装结构 → 装饰。三、场景一咖啡加料最经典的装饰模式业务背景奶茶店的基础饮品是 珍珠奶茶ConcreteComponent顾客可以加料椰果、布丁、奶盖。每加一种料价格增加描述也变化。要求在不改变奶茶类的前提下动态添加配料。说明这是 GoF 原书中装饰模式的经典例子也是软考下午题最爱考的 层层包装增强 场景。3.1 代码实现// Component饮品接口 public interface Beverage { String getDescription(); double getCost(); } // ConcreteComponent珍珠奶茶原始对象 public class PearlMilkTea implements Beverage { Override public String getDescription() { return 珍珠奶茶; } Override public double getCost() { return 12.0; } } // Decorator抽象装饰者所有加料的父类 public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage beverage; } } // ConcreteDecorator椰果加料 public class Coconut extends CondimentDecorator { public Coconut(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 椰果; } Override public double getCost() { return beverage.getCost() 2.0; } } // ConcreteDecorator布丁加料 public class Pudding extends CondimentDecorator { public Pudding(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 布丁; } Override public double getCost() { return beverage.getCost() 3.0; } } // ConcreteDecorator奶盖加料 public class CheeseFoam extends CondimentDecorator { public CheeseFoam(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 奶盖; } Override public double getCost() { return beverage.getCost() 5.0; } public String getTaste() { return 咸香芝士味; } } // Client客户端层层包装 public class Client { public static void main(String[] args) { Beverage beverage new PearlMilkTea(); System.out.println(beverage.getDescription() beverage.getCost()); beverage new Coconut(beverage); System.out.println(beverage.getDescription() beverage.getCost()); beverage new Pudding(beverage); System.out.println(beverage.getDescription() beverage.getCost()); beverage new CheeseFoam(beverage); System.out.println(beverage.getDescription() beverage.getCost()); Beverage combo new CheeseFoam(new Pudding(new Coconut(new PearlMilkTea()))); System.out.println(combo.getDescription() combo.getCost()); } }关键体会每一层装饰者都持有内层对象的引用beverage在getDescription()和getCost()中先调用内层对象的方法再追加自己的逻辑。最终调用像剥洋葱一样从外层逐层向内传递到PearlMilkTea。四、场景二日志装饰实际开发场景前置/后置增强业务背景系统有一个基础的日志记录器SimpleLoggerConcreteComponent只负责写日志到文件。现在需要动态添加以下功能① 给每条日志加时间戳 ② 给每条日志加 IP 地址 ③ 给每条日志加线程名。这些增强功能可以任意组合且不能修改SimpleLogger的源码。说明这是实际开发中 AOP面向切面编程思想的简单实现也是软考下午题中常见的 在方法前后插入增强逻辑 场景。4.1 代码实现// Component日志接口 public interface Logger { void log(String message); } // ConcreteComponent基础日志记录器 public class SimpleLogger implements Logger { Override public void log(String message) { System.out.println([LOG] message); } } // Decorator抽象日志装饰者 public abstract class LoggerDecorator implements Logger { protected Logger logger; public LoggerDecorator(Logger logger) { this.logger logger; } } // ConcreteDecorator时间戳装饰 public class TimestampLogger extends LoggerDecorator { public TimestampLogger(Logger logger) { super(logger); } Override public void log(String message) { String timestamp java.time.LocalDateTime.now().toString(); logger.log([ timestamp ] message); } } // ConcreteDecoratorIP 地址装饰 public class IpLogger extends LoggerDecorator { private String ip; public IpLogger(Logger logger, String ip) { super(logger); this.ip ip; } Override public void log(String message) { logger.log([IP: ip ] message); } } // ConcreteDecorator线程名装饰 public class ThreadLogger extends LoggerDecorator { public ThreadLogger(Logger logger) { super(logger); } Override public void log(String message) { String threadName Thread.currentThread().getName(); logger.log([Thread: threadName ] message); } } // Client客户端自由组合 public class Client { public static void main(String[] args) { Logger logger new SimpleLogger(); logger.log(用户登录成功); Logger logger2 new TimestampLogger(new SimpleLogger()); logger2.log(用户登录成功); Logger logger3 new ThreadLogger( new IpLogger( new TimestampLogger(new SimpleLogger()), 192.168.1.100 ) ); logger3.log(用户登录成功); } }关键体会装饰者可以在被装饰对象方法调用的前后插入增强逻辑本例是前置增强。客户端可以自由组合装饰顺序A(B(C(component)))与B(A(C(component)))的效果可能不同取决于各装饰者的逻辑。五、场景三Java IO 风格BufferedReader 包装 FileReader业务背景Java 标准库中的 IO 流就是装饰模式的经典实现。FileReaderConcreteComponent提供基础的文件读取功能BufferedReaderConcreteDecorator在FileReader的基础上增加了缓冲功能LineNumberReaderConcreteDecorator又增加了行号追踪功能。使用装饰模式模拟这个设计。说明这是软考上午题中常考的 识别 Java IO 中的设计模式 场景。Reader是 ComponentFileReader是 ConcreteComponentBufferedReader是 Decorator。5.1 代码实现// Component数据读取接口 public interface DataReader { String read(); } // ConcreteComponent文件读取器基础功能 public class FileReader implements DataReader { private String filename; public FileReader(String filename) { this.filename filename; } Override public String read() { return 文件 filename 的原始内容; } } // Decorator抽象读取装饰者 public abstract class ReaderDecorator implements DataReader { protected DataReader reader; public ReaderDecorator(DataReader reader) { this.reader reader; } } // ConcreteDecorator缓冲装饰增加缓冲功能 public class BufferedReader extends ReaderDecorator { private String buffer; public BufferedReader(DataReader reader) { super(reader); } Override public String read() { if (buffer null) { buffer reader.read(); System.out.println([BufferedReader] 已缓冲数据); } return buffer; } public String readLine() { return read() 按行读取; } } // ConcreteDecorator行号追踪装饰 public class LineNumberReader extends ReaderDecorator { private int lineNumber 0; public LineNumberReader(DataReader reader) { super(reader); } Override public String read() { lineNumber; String content reader.read(); return [ lineNumber ] content; } public int getLineNumber() { return lineNumber; } } // Client public class Client { public static void main(String[] args) { DataReader reader new FileReader(report.txt); System.out.println(reader.read()); DataReader buffered new BufferedReader(new FileReader(report.txt)); System.out.println(buffered.read()); System.out.println(buffered.read()); DataReader lineReader new LineNumberReader( new BufferedReader(new FileReader(report.txt)) ); System.out.println(lineReader.read()); System.out.println(lineReader.read()); } }六、三种场景对比与演进思路维度场景一咖啡加料场景二日志装饰场景三Java IOComponentBeverageLoggerDataReaderConcreteComponentPearlMilkTeaSimpleLoggerFileReaderDecoratorCondimentDecoratorLoggerDecoratorReaderDecoratorConcreteDecoratorCoconut、Pudding、CheeseFoamTimestampLogger、IpLogger、ThreadLoggerBufferedReader、LineNumberReader增强方式累加描述和价格前置添加元信息前置缓冲/行号新增方法getTaste()奶盖特有无readLine()、getLineNumber()包装层数任意多层任意多层任意多层软考考法下午大题补全价格累加下午大题补全前置增强上午选择题识别 Java IO七、装饰模式 vs 桥接模式 vs 适配器模式核心对比对比项装饰模式桥接模式适配器模式设计目的动态增强对象功能不改变接口分离抽象与实现独立变化转换接口兼容不兼容的类是否改变接口❌ 不改变保持 Component 接口❌ 不改变各自定义接口✅ 改变Adapter 转接口是否新增功能✅ 新增功能❌ 不新增只是分离❌ 不新增只是转换类图结构递归组合Decorator 组合 Component两个独立继承树一个 Adapter 转换类关系类型组合Decorator 持有 Component组合Abstraction 持有 Implementor组合/继承Adapter 持有/继承 AdapteeClient 调用new Decorator(new Component())new Abstraction(new Implementor())new Adapter(new Adaptee())运行时特性可动态叠加多层创建时绑定实现层创建时绑定被适配者软考判断递归包装 接口相同 增强功能两个继承树 组合一个转换类 接口不同记忆口诀装饰是 套娃增强层层包装功能叠加桥接是 拆桥分流两个维度独立适配器是 搭桥翻译接口转换兼容。八、软考高频考点与易混淆辨析8.1 高频考点考点内容模式分类结构型模式GoF 23 正式成员核心四角色Component抽象组件、ConcreteComponent具体组件、Decorator抽象装饰者、ConcreteDecorator具体装饰者核心思想递归组合Decorator 组合 Component且两者实现同一接口装饰者构造器必须接收一个 Component 参数public Decorator(Component c)用于注入被装饰对象委托逻辑Decorator 的operation()中调用component.operation()前后插入增强逻辑与继承的区别装饰是运行时动态增强继承是编译时静态绑定装饰更灵活适用场景① 需要在不影响其他对象的情况下动态、透明地给单个对象添加职责 ② 需要撤销职责时 ③ 不能用子类扩展时Java IO 例子BufferedReader装饰FileReaderDataInputStream装饰InputStream8.2 易混淆辨析装饰 vs 代理模式对比项装饰模式代理模式目的增强对象功能添加新职责控制对象访问权限、延迟加载、远程调用对客户端透明性通常不透明客户端知道自己在用装饰者通常透明客户端以为在调用真实对象是否新增接口可以新增方法如readLine()一般不新增方法保持与真实对象一致典型例子咖啡加料、Java IO 流远程代理、虚拟代理、保护代理软考判断看到 添加职责、增强功能 → 装饰看到 控制访问、延迟加载、权限检查 → 代理关键区分装饰是为了 加功能代理是为了 控访问。如果类图里 Decorator 新增了Component没有的方法 → 装饰如果 Proxy 完全保持原接口只是控制调用时机 → 代理。九、真题风格模拟与代码填空模拟题 1上午选择题以下关于装饰模式的叙述中正确的是。A. 装饰模式属于创建型模式主要用于创建具有增强功能的对象B. 在装饰模式中Decorator 通过继承 Component 来获得被装饰对象的功能C. 装饰模式可以在不改变对象接口的前提下动态地给对象添加额外职责D. 装饰模式与适配器模式的核心区别在于装饰模式转换接口适配器模式增强功能答案C解析A 错误装饰属于结构型模式不是创建型。B 错误Decorator 通过组合持有 Component 引用获得被装饰对象的功能不是继承。如果用继承就无法在运行时动态替换被装饰对象。C 正确这是装饰模式的核心定义。D 错误说反了。装饰模式增强功能不改变接口适配器模式转换接口不改变功能。模拟题 2下午代码填空 — 补全装饰者构造器和委托逻辑某系统使用装饰模式实现数据加密传输。基础组件DataSender负责发送明文EncryptionDecorator在发送前对数据进行加密。请补全1~4。// Component数据发送接口 interface DataSender { void send(String data); } // ConcreteComponent基础发送器发送明文 class PlainSender implements DataSender { public void send(String data) { System.out.println(发送数据: data); } } // Decorator抽象装饰者 abstract class SenderDecorator implements DataSender { protected (1)______ sender; public SenderDecorator((2)______ sender) { this.sender sender; } } // ConcreteDecorator加密装饰者 class EncryptionDecorator extends SenderDecorator { public EncryptionDecorator(DataSender sender) { (3)______; } Override public void send(String data) { String encrypted ENC( data ); System.out.println([加密] 原文: data - 密文: encrypted); (4)______; } } public class Client { public static void main(String[] args) { DataSender sender new EncryptionDecorator(new PlainSender()); sender.send(敏感信息); } }答案(1)DataSender(2)DataSender(3)super(sender)(4)sender.send(encrypted)阅卷要点(1)(2) 必须是DataSenderComponent 接口类型体现面向接口编程。不能写PlainSender等具体类。(3) 必须是super(sender)调用父类 Decorator 的构造器完成注入。如果写this.sender sender虽然功能正确但不符合装饰模式的规范写法应该由父类统一持有引用软考阅卷时可能扣分。(4) 必须是sender.send(encrypted)这是将增强后的数据委托给被装饰者发送。如果写super.sender.send(encrypted)也算对但sender.send(encrypted)更简洁。模拟题 3下午代码填空 — 多层装饰与新增方法某系统使用装饰模式实现图片处理。基础组件Image提供display()方法。BorderDecorator增加边框显示FilterDecorator增加滤镜效果。FilterDecorator还新增了getFilterName()方法。请补全1~3。// Component interface Image { void display(); } // ConcreteComponent class Photo implements Image { public void display() { System.out.println(显示照片); } } // Decorator abstract class ImageDecorator implements Image { protected Image image; public ImageDecorator(Image image) { this.image image; } } // ConcreteDecorator边框装饰 class BorderDecorator extends ImageDecorator { public BorderDecorator(Image image) { super(image); } public void display() { System.out.println(绘制边框); (1)______; } } // ConcreteDecorator滤镜装饰 class FilterDecorator extends ImageDecorator { private String filterName; public FilterDecorator(Image image, String filterName) { super(image); this.filterName filterName; } public void display() { System.out.println(应用滤镜: filterName); (2)______; } public String getFilterName() { return (3)______; } } public class Client { public static void main(String[] args) { Image image new FilterDecorator( new BorderDecorator(new Photo()), 黑白 ); image.display(); } }答案(1)image.display()(2)image.display()(3)filterName阅卷要点(1)(2) 必须是image.display()这是 Decorator 将请求委托给被装饰的 Component 的标准写法。如果写super.image.display()也算对但通常直接写image.display()。注意如果 (1) 写成super.display()会零分因为ImageDecorator没有实现display()方法抽象类调用super.display()会编译错误。(3) 必须是filterName返回装饰者特有的属性。十、常见陷阱与注意事项陷阱 1误认为装饰是创建型模式装饰属于结构型模式因为它解决的是对象之间的结构关系递归组合而不是对象的创建问题。软考上午题如果问以下属于创建型模式的是选项里出现装饰不能选。陷阱 2Decorator 继承 Component 而不是组合这是软考最高频的失分点。装饰模式的核心是组合代替继承❌ 错误class Decorator extends Component继承静态绑定无法动态替换✅ 正确class Decorator { protected Component component; }组合运行时注入软考代码填空里Decorator 必须持有protected Component xxx字段并通过构造器注入。如果 Decorator 直接继承 ConcreteComponent那就是错误的继承滥用。陷阱 3Decorator 的构造器没有注入 Component装饰者的构造器必须接收一个 Component 参数用于注入被装饰对象。如果构造器是无参的如new Decorator()那就无法持有内层对象失去了装饰的意义。软考代码填空里如果 Decorator 构造器参数为空通常直接判错。陷阱 4与适配器模式混淆装饰模式适配器模式接口不变Component 接口接口改变Target → Adaptee 转换目的是增强功能目的是兼容接口可以新增方法一般不新增方法如果类图里 Component 和 Decorator 的方法名完全一致只是 Decorator 在前后加了增强逻辑 → 装饰。如果 Adapter 的方法名与 Adaptee 不同 → 适配器。陷阱 5与代理模式混淆装饰和代理的类图结构几乎一样都实现了同一接口都组合了目标对象。区别在于意图装饰为了添加新功能如加料、加时间戳、加缓冲。代理为了控制访问如延迟加载、权限检查、远程调用。软考中如果题干强调 增加功能、增强、添加职责 → 装饰强调 控制访问、代理、延迟加载 → 代理。陷阱 6多层装饰时的顺序问题多层装饰的顺序可能影响最终效果。例如new A(new B(c))和new B(new A(c))的结果可能不同。软考代码填空通常不涉及顺序问题但选择题可能问 以下哪种包装顺序能得到 XXX 效果需要仔细分析每一层装饰者的前后置逻辑。陷阱 7ConcreteDecorator 忘记调用super.operation()如果 ConcreteDecorator 的operation()中只写了自己的增强逻辑忘记了调用component.operation()或super.operation()那么被装饰对象的原始功能就丢失了。软考阅卷时如果operation()方法里没有委托调用通常会扣分。十一、总结要点内容定义动态地给一个对象添加一些额外的职责就增加功能来说比生成子类更为灵活分类结构型模式GoF 23 正式成员核心四角色Component抽象组件、ConcreteComponent具体组件、Decorator抽象装饰者、ConcreteDecorator具体装饰者核心思想递归组合Decorator 组合 Component且两者实现同一接口运行时层层包装装饰者构造器必须接收 Component 参数注入被装饰对象委托逻辑operation()中调用component.operation()前后插入增强逻辑与继承的区别装饰是运行时动态增强继承是编译时静态绑定装饰更灵活与适配器区别装饰增强功能不改变接口适配器转换接口不改变功能与代理区别装饰添加新功能代理控制对象访问Java 典型例子BufferedReader装饰FileReader、DataInputStream装饰InputStream软考重点代码填空补全 Decorator 构造器 委托调用类图识别递归组合结构与适配器/代理的辨析答题技巧看到 动态添加职责、层层包装、不改变接口 → 装饰看到new Decorator(new Component())的套娃写法 → 确认装饰系列预告下一篇将讲外观模式—— 当子系统接口过于复杂、客户端调用成本过高时如何用 门面 类将复杂子系统的接口统一封装提供一个简洁的高层接口。咱们下回见。
【软考设计模式】装饰模式:“套娃“式增强与动态职责扩展精讲
系列定位软考软件设计师 / 系统架构设计师 — 结构型模式专题第 3 讲考察分值上午题 1-2 分下午题常作为代码填空或类图识别出现难度等级⭐⭐⭐☆☆结构像套娃层层包装掌握递归组合后很稳一、考纲定位与模式定义1.1 考纲要求装饰模式在软考中属于结构型模式的基础内容。考察形式包括上午选择题判断场景描述所属模式识别装饰模式与桥接模式、适配器模式的区别判断类图中是否存在 递归组合 结构Decorator 组合 Component下午设计题补全 Decorator 抽象类的构造器注入 Component和委托逻辑补全 ConcreteDecorator 中新增的职责方法识别类图中 Component、ConcreteComponent、Decorator、ConcreteDecorator 四个角色1.2 模式定义装饰模式动态地给一个对象添加一些额外的职责就增加功能来说装饰模式比生成子类更为灵活。核心意图当需要在不改变原有对象接口的前提下给对象动态添加新功能时不用继承继承是静态的、编译时确定的而是用包装Wrapper的方式在运行时层层套娃。每一层装饰者都持有被装饰对象的引用在调用被装饰对象方法的前后插入自己的增强逻辑。通俗理解你去奶茶店点一杯奶茶。基础款是 珍珠奶茶ConcreteComponent你可以加料 椰果ConcreteDecoratorA、 布丁ConcreteDecoratorB、 奶盖ConcreteDecoratorC。每加一种料都是在原有奶茶的基础上包装一层最终你拿到的是一杯 奶盖 布丁 椰果 珍珠奶茶。每一层都不改变奶茶的 喝 这个接口但增加了新的口感职责。二、UML 类图与角色划分┌────────────────────────────────────────────────────────────┐ │ Client │ │ main() │ │ ├── Component c new ConcreteComponent(); │ │ ├── c new ConcreteDecoratorA(c); // 第一层包装 │ │ ├── c new ConcreteDecoratorB(c); // 第二层包装 │ │ └── c.operation(); // 逐层调用最终到 ConcreteComponent │ └─────────────────────────────┬──────────────────────────────┘ │ uses ▼ ┌────────────────────────────────────────────────────────────┐ │ interface │ │ Component │ │ operation() │ └─────────────────────────────┬──────────────────────────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ ConcreteComponent │ │ Decorator │ │ (被装饰的原始对象) │ │ - component: │ │ operation() │ │ Component │ │ (原始业务逻辑) │ │ operation() { │ │ │ │ // 可前置增强 │ │ │ │ component. │ │ │ │ operation(); │ │ │ │ // 可后置增强 │ │ │ │ } │ └────────────────────────┘ └──────────┬─────────────┘ │ ┌──────────────────┴──────────────────┐ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ ConcreteDecoratorA │ │ ConcreteDecoratorB │ │ operation() { │ │ operation() { │ │ // 增强A │ │ // 增强B │ │ super.op(); │ │ super.op(); │ │ // 增强A │ │ // 增强B │ │ } │ │ } │ │ addedBehavior() │ │ addedBehavior() │ └─────────────────────┘ └─────────────────────┘角色职责软考填空关键词Component抽象组件定义一个对象接口规范所有对象被装饰者和装饰者的统一接口interface/abstract classoperation()ConcreteComponent具体组件被装饰的原始对象实现 Component 接口完成核心功能原始业务类如Coffee、FileReaderDecorator抽象装饰者实现 Component 接口组合一个 Component 引用将请求委托给被装饰对象abstract classprotected Component component 构造器注入ConcreteDecorator具体装饰者继承 Decorator在operation()中调用父类委托方法的前后添加自己的增强逻辑extends Decoratorsuper.operation() 新增职责方法Client客户端创建 ConcreteComponent并用 ConcreteDecorator 层层包装最终调用operation()new DecoratorA(new DecoratorB(new Component()))类图识别要点软考类图中装饰模式的核心特征是Decorator 类与 Component 是组合关系Decorator 持有 Component 引用且 Decorator 与 Component 实现同一个接口或继承同一个抽象类。如果类图里只有一个转换类连接两个不同接口 → 适配器如果有两个独立继承树 → 桥接如果是一个递归包装结构 → 装饰。三、场景一咖啡加料最经典的装饰模式业务背景奶茶店的基础饮品是 珍珠奶茶ConcreteComponent顾客可以加料椰果、布丁、奶盖。每加一种料价格增加描述也变化。要求在不改变奶茶类的前提下动态添加配料。说明这是 GoF 原书中装饰模式的经典例子也是软考下午题最爱考的 层层包装增强 场景。3.1 代码实现// Component饮品接口 public interface Beverage { String getDescription(); double getCost(); } // ConcreteComponent珍珠奶茶原始对象 public class PearlMilkTea implements Beverage { Override public String getDescription() { return 珍珠奶茶; } Override public double getCost() { return 12.0; } } // Decorator抽象装饰者所有加料的父类 public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage beverage; } } // ConcreteDecorator椰果加料 public class Coconut extends CondimentDecorator { public Coconut(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 椰果; } Override public double getCost() { return beverage.getCost() 2.0; } } // ConcreteDecorator布丁加料 public class Pudding extends CondimentDecorator { public Pudding(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 布丁; } Override public double getCost() { return beverage.getCost() 3.0; } } // ConcreteDecorator奶盖加料 public class CheeseFoam extends CondimentDecorator { public CheeseFoam(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() 奶盖; } Override public double getCost() { return beverage.getCost() 5.0; } public String getTaste() { return 咸香芝士味; } } // Client客户端层层包装 public class Client { public static void main(String[] args) { Beverage beverage new PearlMilkTea(); System.out.println(beverage.getDescription() beverage.getCost()); beverage new Coconut(beverage); System.out.println(beverage.getDescription() beverage.getCost()); beverage new Pudding(beverage); System.out.println(beverage.getDescription() beverage.getCost()); beverage new CheeseFoam(beverage); System.out.println(beverage.getDescription() beverage.getCost()); Beverage combo new CheeseFoam(new Pudding(new Coconut(new PearlMilkTea()))); System.out.println(combo.getDescription() combo.getCost()); } }关键体会每一层装饰者都持有内层对象的引用beverage在getDescription()和getCost()中先调用内层对象的方法再追加自己的逻辑。最终调用像剥洋葱一样从外层逐层向内传递到PearlMilkTea。四、场景二日志装饰实际开发场景前置/后置增强业务背景系统有一个基础的日志记录器SimpleLoggerConcreteComponent只负责写日志到文件。现在需要动态添加以下功能① 给每条日志加时间戳 ② 给每条日志加 IP 地址 ③ 给每条日志加线程名。这些增强功能可以任意组合且不能修改SimpleLogger的源码。说明这是实际开发中 AOP面向切面编程思想的简单实现也是软考下午题中常见的 在方法前后插入增强逻辑 场景。4.1 代码实现// Component日志接口 public interface Logger { void log(String message); } // ConcreteComponent基础日志记录器 public class SimpleLogger implements Logger { Override public void log(String message) { System.out.println([LOG] message); } } // Decorator抽象日志装饰者 public abstract class LoggerDecorator implements Logger { protected Logger logger; public LoggerDecorator(Logger logger) { this.logger logger; } } // ConcreteDecorator时间戳装饰 public class TimestampLogger extends LoggerDecorator { public TimestampLogger(Logger logger) { super(logger); } Override public void log(String message) { String timestamp java.time.LocalDateTime.now().toString(); logger.log([ timestamp ] message); } } // ConcreteDecoratorIP 地址装饰 public class IpLogger extends LoggerDecorator { private String ip; public IpLogger(Logger logger, String ip) { super(logger); this.ip ip; } Override public void log(String message) { logger.log([IP: ip ] message); } } // ConcreteDecorator线程名装饰 public class ThreadLogger extends LoggerDecorator { public ThreadLogger(Logger logger) { super(logger); } Override public void log(String message) { String threadName Thread.currentThread().getName(); logger.log([Thread: threadName ] message); } } // Client客户端自由组合 public class Client { public static void main(String[] args) { Logger logger new SimpleLogger(); logger.log(用户登录成功); Logger logger2 new TimestampLogger(new SimpleLogger()); logger2.log(用户登录成功); Logger logger3 new ThreadLogger( new IpLogger( new TimestampLogger(new SimpleLogger()), 192.168.1.100 ) ); logger3.log(用户登录成功); } }关键体会装饰者可以在被装饰对象方法调用的前后插入增强逻辑本例是前置增强。客户端可以自由组合装饰顺序A(B(C(component)))与B(A(C(component)))的效果可能不同取决于各装饰者的逻辑。五、场景三Java IO 风格BufferedReader 包装 FileReader业务背景Java 标准库中的 IO 流就是装饰模式的经典实现。FileReaderConcreteComponent提供基础的文件读取功能BufferedReaderConcreteDecorator在FileReader的基础上增加了缓冲功能LineNumberReaderConcreteDecorator又增加了行号追踪功能。使用装饰模式模拟这个设计。说明这是软考上午题中常考的 识别 Java IO 中的设计模式 场景。Reader是 ComponentFileReader是 ConcreteComponentBufferedReader是 Decorator。5.1 代码实现// Component数据读取接口 public interface DataReader { String read(); } // ConcreteComponent文件读取器基础功能 public class FileReader implements DataReader { private String filename; public FileReader(String filename) { this.filename filename; } Override public String read() { return 文件 filename 的原始内容; } } // Decorator抽象读取装饰者 public abstract class ReaderDecorator implements DataReader { protected DataReader reader; public ReaderDecorator(DataReader reader) { this.reader reader; } } // ConcreteDecorator缓冲装饰增加缓冲功能 public class BufferedReader extends ReaderDecorator { private String buffer; public BufferedReader(DataReader reader) { super(reader); } Override public String read() { if (buffer null) { buffer reader.read(); System.out.println([BufferedReader] 已缓冲数据); } return buffer; } public String readLine() { return read() 按行读取; } } // ConcreteDecorator行号追踪装饰 public class LineNumberReader extends ReaderDecorator { private int lineNumber 0; public LineNumberReader(DataReader reader) { super(reader); } Override public String read() { lineNumber; String content reader.read(); return [ lineNumber ] content; } public int getLineNumber() { return lineNumber; } } // Client public class Client { public static void main(String[] args) { DataReader reader new FileReader(report.txt); System.out.println(reader.read()); DataReader buffered new BufferedReader(new FileReader(report.txt)); System.out.println(buffered.read()); System.out.println(buffered.read()); DataReader lineReader new LineNumberReader( new BufferedReader(new FileReader(report.txt)) ); System.out.println(lineReader.read()); System.out.println(lineReader.read()); } }六、三种场景对比与演进思路维度场景一咖啡加料场景二日志装饰场景三Java IOComponentBeverageLoggerDataReaderConcreteComponentPearlMilkTeaSimpleLoggerFileReaderDecoratorCondimentDecoratorLoggerDecoratorReaderDecoratorConcreteDecoratorCoconut、Pudding、CheeseFoamTimestampLogger、IpLogger、ThreadLoggerBufferedReader、LineNumberReader增强方式累加描述和价格前置添加元信息前置缓冲/行号新增方法getTaste()奶盖特有无readLine()、getLineNumber()包装层数任意多层任意多层任意多层软考考法下午大题补全价格累加下午大题补全前置增强上午选择题识别 Java IO七、装饰模式 vs 桥接模式 vs 适配器模式核心对比对比项装饰模式桥接模式适配器模式设计目的动态增强对象功能不改变接口分离抽象与实现独立变化转换接口兼容不兼容的类是否改变接口❌ 不改变保持 Component 接口❌ 不改变各自定义接口✅ 改变Adapter 转接口是否新增功能✅ 新增功能❌ 不新增只是分离❌ 不新增只是转换类图结构递归组合Decorator 组合 Component两个独立继承树一个 Adapter 转换类关系类型组合Decorator 持有 Component组合Abstraction 持有 Implementor组合/继承Adapter 持有/继承 AdapteeClient 调用new Decorator(new Component())new Abstraction(new Implementor())new Adapter(new Adaptee())运行时特性可动态叠加多层创建时绑定实现层创建时绑定被适配者软考判断递归包装 接口相同 增强功能两个继承树 组合一个转换类 接口不同记忆口诀装饰是 套娃增强层层包装功能叠加桥接是 拆桥分流两个维度独立适配器是 搭桥翻译接口转换兼容。八、软考高频考点与易混淆辨析8.1 高频考点考点内容模式分类结构型模式GoF 23 正式成员核心四角色Component抽象组件、ConcreteComponent具体组件、Decorator抽象装饰者、ConcreteDecorator具体装饰者核心思想递归组合Decorator 组合 Component且两者实现同一接口装饰者构造器必须接收一个 Component 参数public Decorator(Component c)用于注入被装饰对象委托逻辑Decorator 的operation()中调用component.operation()前后插入增强逻辑与继承的区别装饰是运行时动态增强继承是编译时静态绑定装饰更灵活适用场景① 需要在不影响其他对象的情况下动态、透明地给单个对象添加职责 ② 需要撤销职责时 ③ 不能用子类扩展时Java IO 例子BufferedReader装饰FileReaderDataInputStream装饰InputStream8.2 易混淆辨析装饰 vs 代理模式对比项装饰模式代理模式目的增强对象功能添加新职责控制对象访问权限、延迟加载、远程调用对客户端透明性通常不透明客户端知道自己在用装饰者通常透明客户端以为在调用真实对象是否新增接口可以新增方法如readLine()一般不新增方法保持与真实对象一致典型例子咖啡加料、Java IO 流远程代理、虚拟代理、保护代理软考判断看到 添加职责、增强功能 → 装饰看到 控制访问、延迟加载、权限检查 → 代理关键区分装饰是为了 加功能代理是为了 控访问。如果类图里 Decorator 新增了Component没有的方法 → 装饰如果 Proxy 完全保持原接口只是控制调用时机 → 代理。九、真题风格模拟与代码填空模拟题 1上午选择题以下关于装饰模式的叙述中正确的是。A. 装饰模式属于创建型模式主要用于创建具有增强功能的对象B. 在装饰模式中Decorator 通过继承 Component 来获得被装饰对象的功能C. 装饰模式可以在不改变对象接口的前提下动态地给对象添加额外职责D. 装饰模式与适配器模式的核心区别在于装饰模式转换接口适配器模式增强功能答案C解析A 错误装饰属于结构型模式不是创建型。B 错误Decorator 通过组合持有 Component 引用获得被装饰对象的功能不是继承。如果用继承就无法在运行时动态替换被装饰对象。C 正确这是装饰模式的核心定义。D 错误说反了。装饰模式增强功能不改变接口适配器模式转换接口不改变功能。模拟题 2下午代码填空 — 补全装饰者构造器和委托逻辑某系统使用装饰模式实现数据加密传输。基础组件DataSender负责发送明文EncryptionDecorator在发送前对数据进行加密。请补全1~4。// Component数据发送接口 interface DataSender { void send(String data); } // ConcreteComponent基础发送器发送明文 class PlainSender implements DataSender { public void send(String data) { System.out.println(发送数据: data); } } // Decorator抽象装饰者 abstract class SenderDecorator implements DataSender { protected (1)______ sender; public SenderDecorator((2)______ sender) { this.sender sender; } } // ConcreteDecorator加密装饰者 class EncryptionDecorator extends SenderDecorator { public EncryptionDecorator(DataSender sender) { (3)______; } Override public void send(String data) { String encrypted ENC( data ); System.out.println([加密] 原文: data - 密文: encrypted); (4)______; } } public class Client { public static void main(String[] args) { DataSender sender new EncryptionDecorator(new PlainSender()); sender.send(敏感信息); } }答案(1)DataSender(2)DataSender(3)super(sender)(4)sender.send(encrypted)阅卷要点(1)(2) 必须是DataSenderComponent 接口类型体现面向接口编程。不能写PlainSender等具体类。(3) 必须是super(sender)调用父类 Decorator 的构造器完成注入。如果写this.sender sender虽然功能正确但不符合装饰模式的规范写法应该由父类统一持有引用软考阅卷时可能扣分。(4) 必须是sender.send(encrypted)这是将增强后的数据委托给被装饰者发送。如果写super.sender.send(encrypted)也算对但sender.send(encrypted)更简洁。模拟题 3下午代码填空 — 多层装饰与新增方法某系统使用装饰模式实现图片处理。基础组件Image提供display()方法。BorderDecorator增加边框显示FilterDecorator增加滤镜效果。FilterDecorator还新增了getFilterName()方法。请补全1~3。// Component interface Image { void display(); } // ConcreteComponent class Photo implements Image { public void display() { System.out.println(显示照片); } } // Decorator abstract class ImageDecorator implements Image { protected Image image; public ImageDecorator(Image image) { this.image image; } } // ConcreteDecorator边框装饰 class BorderDecorator extends ImageDecorator { public BorderDecorator(Image image) { super(image); } public void display() { System.out.println(绘制边框); (1)______; } } // ConcreteDecorator滤镜装饰 class FilterDecorator extends ImageDecorator { private String filterName; public FilterDecorator(Image image, String filterName) { super(image); this.filterName filterName; } public void display() { System.out.println(应用滤镜: filterName); (2)______; } public String getFilterName() { return (3)______; } } public class Client { public static void main(String[] args) { Image image new FilterDecorator( new BorderDecorator(new Photo()), 黑白 ); image.display(); } }答案(1)image.display()(2)image.display()(3)filterName阅卷要点(1)(2) 必须是image.display()这是 Decorator 将请求委托给被装饰的 Component 的标准写法。如果写super.image.display()也算对但通常直接写image.display()。注意如果 (1) 写成super.display()会零分因为ImageDecorator没有实现display()方法抽象类调用super.display()会编译错误。(3) 必须是filterName返回装饰者特有的属性。十、常见陷阱与注意事项陷阱 1误认为装饰是创建型模式装饰属于结构型模式因为它解决的是对象之间的结构关系递归组合而不是对象的创建问题。软考上午题如果问以下属于创建型模式的是选项里出现装饰不能选。陷阱 2Decorator 继承 Component 而不是组合这是软考最高频的失分点。装饰模式的核心是组合代替继承❌ 错误class Decorator extends Component继承静态绑定无法动态替换✅ 正确class Decorator { protected Component component; }组合运行时注入软考代码填空里Decorator 必须持有protected Component xxx字段并通过构造器注入。如果 Decorator 直接继承 ConcreteComponent那就是错误的继承滥用。陷阱 3Decorator 的构造器没有注入 Component装饰者的构造器必须接收一个 Component 参数用于注入被装饰对象。如果构造器是无参的如new Decorator()那就无法持有内层对象失去了装饰的意义。软考代码填空里如果 Decorator 构造器参数为空通常直接判错。陷阱 4与适配器模式混淆装饰模式适配器模式接口不变Component 接口接口改变Target → Adaptee 转换目的是增强功能目的是兼容接口可以新增方法一般不新增方法如果类图里 Component 和 Decorator 的方法名完全一致只是 Decorator 在前后加了增强逻辑 → 装饰。如果 Adapter 的方法名与 Adaptee 不同 → 适配器。陷阱 5与代理模式混淆装饰和代理的类图结构几乎一样都实现了同一接口都组合了目标对象。区别在于意图装饰为了添加新功能如加料、加时间戳、加缓冲。代理为了控制访问如延迟加载、权限检查、远程调用。软考中如果题干强调 增加功能、增强、添加职责 → 装饰强调 控制访问、代理、延迟加载 → 代理。陷阱 6多层装饰时的顺序问题多层装饰的顺序可能影响最终效果。例如new A(new B(c))和new B(new A(c))的结果可能不同。软考代码填空通常不涉及顺序问题但选择题可能问 以下哪种包装顺序能得到 XXX 效果需要仔细分析每一层装饰者的前后置逻辑。陷阱 7ConcreteDecorator 忘记调用super.operation()如果 ConcreteDecorator 的operation()中只写了自己的增强逻辑忘记了调用component.operation()或super.operation()那么被装饰对象的原始功能就丢失了。软考阅卷时如果operation()方法里没有委托调用通常会扣分。十一、总结要点内容定义动态地给一个对象添加一些额外的职责就增加功能来说比生成子类更为灵活分类结构型模式GoF 23 正式成员核心四角色Component抽象组件、ConcreteComponent具体组件、Decorator抽象装饰者、ConcreteDecorator具体装饰者核心思想递归组合Decorator 组合 Component且两者实现同一接口运行时层层包装装饰者构造器必须接收 Component 参数注入被装饰对象委托逻辑operation()中调用component.operation()前后插入增强逻辑与继承的区别装饰是运行时动态增强继承是编译时静态绑定装饰更灵活与适配器区别装饰增强功能不改变接口适配器转换接口不改变功能与代理区别装饰添加新功能代理控制对象访问Java 典型例子BufferedReader装饰FileReader、DataInputStream装饰InputStream软考重点代码填空补全 Decorator 构造器 委托调用类图识别递归组合结构与适配器/代理的辨析答题技巧看到 动态添加职责、层层包装、不改变接口 → 装饰看到new Decorator(new Component())的套娃写法 → 确认装饰系列预告下一篇将讲外观模式—— 当子系统接口过于复杂、客户端调用成本过高时如何用 门面 类将复杂子系统的接口统一封装提供一个简洁的高层接口。咱们下回见。