
1. 什么是装饰器模式装饰器模式Decorator Pattern是一种结构型设计模式它允许在不改变现有对象结构的情况下动态地给该对象增加一些额外的职责。就增加功能来说装饰器模式比生成子类更为灵活。核心思想通过创建一个包装对象即装饰器来包裹真实对象并在保持真实对象接口一致的前提下提供额外的功能。2. 为什么需要装饰器模式在软件开发中我们经常遇到需要为对象动态添加功能的情况。如果使用继承会导致类爆炸子类数量急剧增长且功能组合不灵活。装饰器模式提供了一种更优雅的解决方案。适用场景在不影响其他对象的情况下以动态、透明的方式给单个对象添加职责。当不能采用继承来扩展功能或者继承会导致类爆炸时。需要为对象添加的功能可以随意组合和撤销。3. 装饰器模式的结构装饰器模式主要包含以下角色Component抽象构件定义对象的接口可以给这些对象动态地添加职责。ConcreteComponent具体构件定义具体的对象即被装饰的原始对象。Decorator抽象装饰器继承或实现 Component并持有一个 Component 对象的引用。它是所有具体装饰器的父类。ConcreteDecorator具体装饰器负责向构件添加新的职责。UML 类图示意持有«interface»Componentoperation() : StringConcreteComponentoperation() : StringDecorator-component: ComponentDecorator(Component component)operation() : StringConcreteDecoratorAoperation() : StringaddedBehavior() : StringConcreteDecoratorBoperation() : StringaddedBehavior() : String4. 从零实现一个咖啡店的例子让我们通过一个经典的咖啡店例子来理解装饰器模式。咖啡店提供多种咖啡如浓缩咖啡、美式咖啡并允许顾客添加各种调料如牛奶、摩卡、奶泡每种调料都会影响最终的价格和描述。4.1 定义抽象构件饮料// 抽象构件饮料publicabstractclassBeverage{protectedStringdescription未知饮料;publicStringgetDescription(){returndescription;}// 计算价格由子类实现publicabstractdoublecost();}4.2 实现具体构件具体咖啡// 具体构件浓缩咖啡publicclassEspressoextendsBeverage{publicEspresso(){description浓缩咖啡;}Overridepublicdoublecost(){return1.99;}}// 具体构件美式咖啡publicclassHouseBlendextendsBeverage{publicHouseBlend(){description美式咖啡;}Overridepublicdoublecost(){return0.89;}}4.3 定义抽象装饰器调料// 抽象装饰器调料继承自 Beverage并持有 Beverage 的引用publicabstractclassCondimentDecoratorextendsBeverage{// 持有被装饰对象的引用protectedBeveragebeverage;// 所有具体装饰器都必须重新实现 getDescription 方法publicabstractStringgetDescription();}4.4 实现具体装饰器具体调料// 具体装饰器牛奶publicclassMilkextendsCondimentDecorator{publicMilk(Beveragebeverage){this.beveragebeverage;}OverridepublicStringgetDescription(){returnbeverage.getDescription()加牛奶;}Overridepublicdoublecost(){returnbeverage.cost()0.20;}}// 具体装饰器摩卡publicclassMochaextendsCondimentDecorator{publicMocha(Beveragebeverage){this.beveragebeverage;}OverridepublicStringgetDescription(){returnbeverage.getDescription()加摩卡;}Overridepublicdoublecost(){returnbeverage.cost()0.30;}}// 具体装饰器奶泡publicclassWhipextendsCondimentDecorator{publicWhip(Beveragebeverage){this.beveragebeverage;}OverridepublicStringgetDescription(){returnbeverage.getDescription()加奶泡;}Overridepublicdoublecost(){returnbeverage.cost()0.15;}}4.5 客户端使用publicclassCoffeeShop{publicstaticvoidmain(String[]args){// 1. 点一杯浓缩咖啡不加任何调料Beveragebeverage1newEspresso();System.out.println(beverage1.getDescription() beverage1.cost());// 2. 点一杯美式咖啡加双份摩卡和奶泡Beveragebeverage2newHouseBlend();beverage2newMocha(beverage2);// 第一次装饰加摩卡beverage2newMocha(beverage2);// 第二次装饰再加一份摩卡beverage2newWhip(beverage2);// 第三次装饰加奶泡System.out.println(beverage2.getDescription() beverage2.cost());// 3. 点一杯浓缩咖啡加牛奶和摩卡Beveragebeverage3newEspresso();beverage3newMilk(beverage3);beverage3newMocha(beverage3);System.out.println(beverage3.getDescription() beverage3.cost());}}输出结果浓缩咖啡 1.99 美式咖啡加摩卡加摩卡加奶泡 1.79 浓缩咖啡加牛奶加摩卡 2.495. 装饰器模式在 JDK 和主流框架中的应用5.1 Java I/O 流Java I/O 库是装饰器模式的经典应用。InputStream/OutputStream及其子类构成了一个庞大的装饰器体系。// 使用装饰器模式层层包装流try(InputStreaminnewFileInputStream(test.txt);// BufferedInputStream 是装饰器装饰了 FileInputStreamInputStreambufferedInnewBufferedInputStream(in);// DataInputStream 是另一个装饰器装饰了 BufferedInputStreamDataInputStreamdataInnewDataInputStream(bufferedIn)){// 读取数据intdatadataIn.readInt();}catch(IOExceptione){e.printStackTrace();}5.2 Java Collections 工具类Collections.unmodifiableXXX()方法返回的集合也是装饰器。ListStringoriginalListnewArrayList();originalList.add(A);originalList.add(B);// unmodifiableList 返回一个装饰器它包装了原始列表禁止修改操作ListStringunmodifiableCollections.unmodifiableList(originalList);// unmodifiable.add(C); // 抛出 UnsupportedOperationException5.3 Servlet API 中的 Request/Response 包装在 Java Web 开发中可以通过HttpServletRequestWrapper和HttpServletResponseWrapper装饰原始的请求和响应对象。publicclassLoggingRequestWrapperextendsHttpServletRequestWrapper{publicLoggingRequestWrapper(HttpServletRequestrequest){super(request);}OverridepublicStringgetParameter(Stringname){Stringvaluesuper.getParameter(name);// 添加日志记录功能System.out.println(获取参数: name value);returnvalue;}}5.4 Spring FrameworkSpring 的BeanPostProcessor、AOP 中的拦截器链以及WebFlux中的WebFilter链都体现了装饰器模式的思想。6. 装饰器模式的优缺点优点开闭原则无需修改现有代码即可扩展对象功能。组合优于继承通过组合不同的装饰器可以动态地、灵活地组合功能避免了类爆炸。职责分离可以将复杂功能分解为一系列简单的装饰器类。运行时动态添加功能可以在运行时动态地添加或移除。缺点增加系统复杂度会引入大量的小类使系统变得复杂。调试困难由于对象被多层包装调试时可能难以追踪到具体的层次。设计难度需要正确设计装饰器的接口确保装饰器可以透明地替换原始对象。7. 装饰器模式 vs. 其他模式7.1 装饰器模式 vs. 适配器模式装饰器模式不改变接口只增强功能。适配器模式改变接口使不兼容的接口能够一起工作。7.2 装饰器模式 vs. 代理模式装饰器模式关注动态添加功能装饰器与被装饰对象实现相同的接口。代理模式关注控制访问代理类控制对真实对象的访问可能实现相同的接口也可能不实现。7.3 装饰器模式 vs. 组合模式装饰器模式为单个对象添加功能装饰器持有单个组件。组合模式将对象组合成树形结构以表示部分-整体的层次结构允许客户端统一对待单个对象和组合对象。8. 实战进阶实现一个带缓存的 HTTP 客户端装饰器让我们实现一个更贴近实际开发的例子一个简单的 HTTP 客户端通过装饰器为其添加缓存功能。8.1 定义抽象构件HTTP 客户端接口publicinterfaceHttpClient{Stringget(Stringurl);}###8.2实现具体构件基础HTTP客户端 javaimportjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.net.HttpURLConnection;importjava.net.URL;publicclassBasicHttpClientimplementsHttpClient{OverridepublicStringget(Stringurl){try{// 1. 创建HTTP连接使用Java标准库建立到指定URL的连接HttpURLConnectionconn(HttpURLConnection)newURL(url).openConnection();// 2. 设置请求方法指定为GET请求conn.setRequestMethod(GET);// 3. 读取响应流通过输入流获取服务器返回的数据BufferedReaderreadernewBufferedReader(newInputStreamReader(conn.getInputStream()));StringBuilderresponsenewStringBuilder();Stringline;// 4. 逐行读取响应内容将服务器返回的数据拼接成完整字符串while((linereader.readLine())!null){response.append(line);}reader.close();// 5. 返回响应字符串将完整的响应内容返回给调用者returnresponse.toString();}catch(Exceptione){// 6. 异常处理将检查异常转换为运行时异常简化调用方代码thrownewRuntimeException(HTTP请求失败: url,e);}}}8.3 实现装饰器带缓存的 HTTP 客户端importjava.util.HashMap;importjava.util.Map;publicclassCachingHttpClientDecoratorimplementsHttpClient{// 1. 持有被装饰对象的引用这是装饰器模式的核心通过组合方式扩展功能privatefinalHttpClienthttpClient;// 2. 缓存存储使用HashMap作为简单的内存缓存键为URL值为响应内容privatefinalMapString,StringcachenewHashMap();// 3. 构造函数接收被装饰的HttpClient对象遵循装饰器模式的标准构造方式publicCachingHttpClientDecorator(HttpClienthttpClient){this.httpClienthttpClient;}OverridepublicStringget(Stringurl){// 4. 缓存检查首先检查请求的URL是否已经在缓存中存在if(cache.containsKey(url)){// 5. 缓存命中如果缓存中存在直接返回缓存的结果避免重复网络请求System.out.println(从缓存获取: url);returncache.get(url);}// 6. 缓存未命中如果缓存中不存在执行实际的HTTP请求System.out.println(发送HTTP请求: url);// 7. 委托给被装饰对象调用原始HttpClient的get方法执行实际网络请求StringresponsehttpClient.get(url);// 8. 缓存存储将获取到的响应存入缓存供后续相同请求使用cache.put(url,response);// 9. 返回响应将获取到的响应返回给调用者returnresponse;}// 10. 清空缓存方法提供缓存管理功能可以手动清空所有缓存数据publicvoidclearCache(){cache.clear();}}8.4 客户端使用publicclassHttpClientDemo{publicstaticvoidmain(String[]args){// 1. 创建基础HTTP客户端这是被装饰的具体构件对象HttpClientclientnewBasicHttpClient();// 2. 用缓存装饰器包装它创建装饰器对象将基础客户端作为参数传入HttpClientcachingClientnewCachingHttpClientDecorator(client);Stringurlhttps://api.example.com/data;// 3. 第一次请求会真正发送HTTP请求缓存未命中场景Stringresponse1cachingClient.get(url);System.out.println(第一次响应长度: response1.length());// 4. 第二次请求相同URL从缓存获取缓存命中场景Stringresponse2cachingClient.get(url);System.out.println(第二次响应长度: response2.length());// 5. 验证两次响应相同确认缓存机制正常工作两次获取的内容一致System.out.println(两次响应是否相同: response1.equals(response2));}}/* 运行结果示例 发送HTTP请求: https://api.example.com/data 第一次响应长度: 1250 从缓存获取: https://api.example.com/data 第二次响应长度: 1250 两次响应是否相同: true 解释缓存命中和未命中的过程 1. 第一次调用 cachingClient.get(url) 时 - 缓存中不存在该URL的响应缓存未命中 - 打印发送HTTP请求: https://api.example.com/data - 执行实际的HTTP请求获取响应 - 将响应存入缓存 - 返回响应内容 2. 第二次调用 cachingClient.get(url) 时相同URL - 缓存中已存在该URL的响应缓存命中 - 打印从缓存获取: https://api.example.com/data - 直接从缓存返回响应不再发送HTTP请求 - 显著提升性能减少网络开销 3. 验证结果 - 两次响应长度相同说明缓存正常工作 - 两次响应内容相同验证了缓存的一致性 */9. 最佳实践与注意事项保持接口一致性装饰器必须与被装饰对象实现相同的接口这是透明性的基础。避免过度装饰过多的装饰层会影响性能并让代码难以理解。考虑线程安全如果装饰器有状态如缓存需要考虑线程安全问题。与工厂模式结合可以使用工厂模式来创建装饰后的对象简化客户端代码。文档化装饰顺序当多个装饰器可以按不同顺序应用时需要明确文档说明顺序的影响。10. 总结装饰器模式是 Java 设计模式中非常实用的一种结构型模式它通过组合而非继承的方式实现了功能的动态扩展。从简单的咖啡店例子到复杂的 HTTP 客户端缓存装饰器我们可以看到装饰器模式在实际开发中的强大威力。核心要点回顾装饰器模式通过包装对象来动态添加功能符合开闭原则。Java I/O 流是装饰器模式的经典实现。装饰器模式适合需要动态、透明地扩展对象功能的场景。注意装饰器模式的缺点会增加系统复杂度和调试难度。掌握装饰器模式能够让你在面临功能扩展需求时拥有比继承更灵活、更优雅的解决方案。