大家好,欢迎来到IT知识分享网。
在日常开发中,我们往往忽视了设计模式的重要性。这可能是因为项目时间紧迫,或者对设计模式理解不深。其实,很多时候我们可能在不经意间已经使用了某些模式。
重要的是要有意识地学习和应用,让代码更加优雅和高效。也许是时候重新审视我们的编程实践,将设计模式融入其中了。
今天由浅入深,重学【装饰器模式】,让我们一起“重学设计模式”。
装饰器模式(Decorator Pattern)是一种结构型设计模式,允许通过一种灵活的方式动态地为对象添加新的行为或职责,而无需修改其原始代码。这种模式提供了比继承更有弹性的替代方案,通过将对象包装在一系列装饰器类中,可以在运行时组合出各种行为。
一、装饰器模式的结构
- Component(组件):定义一个对象接口,装饰器和被装饰对象必须实现的公共接口。
- ConcreteComponent(具体组件):实现Component接口的类,是可以被装饰的类。
- Decorator(装饰器):实现Component接口,同时持有Component类型的成员变量(可以是原始对象或其他装饰器),通过组合将功能动态扩展到该对象。
- ConcreteDecorator(具体装饰器):实现装饰器具体行为扩展的类,通过调用被装饰对象的行为,并在此基础上添加新的功能。
二、不使用装饰器模式时
假设我们有一个基本的电脑类,并想为它动态添加不同的配置(比如CPU、内存、GPU)。
当需要新增配置时,在Computer类中新增即可,清晰明了,没有一个ifelse解决不了的。
public class Computer { private boolean hasRAMUpgrade; private boolean hasGPUUpgrade; private boolean hasStorageUpgrade; // 基础电脑的价格 private double baseCost = 500; public Computer(boolean hasRAMUpgrade, boolean hasGPUUpgrade, boolean hasStorageUpgrade) { this.hasRAMUpgrade = hasRAMUpgrade; this.hasGPUUpgrade = hasGPUUpgrade; this.hasStorageUpgrade = hasStorageUpgrade; } public String getDescription() { String description = "Basic Computer"; if (hasRAMUpgrade) { description += ", with 16GB RAM"; } if (hasGPUUpgrade) { description += ", with NVIDIA GPU"; } if (hasStorageUpgrade) { description += ", with 1TB SSD"; } return description; } public double cost() { double totalCost = baseCost; if (hasRAMUpgrade) { totalCost += 150; } if (hasGPUUpgrade) { totalCost += 300; } if (hasStorageUpgrade) { totalCost += 200; } return totalCost; } } package com.guor.designPattern.decorator.no; public class Test { public static void main(String[] args) { // 用户选择了升级内存和显卡 Computer myComputer = new Computer(true, true, true); System.out.println(myComputer.getDescription() + " costs #34; + myComputer.cost()); } }
三、使用装饰器模式优化
当需要新增配置时,直接实现ComputerDecorator,重写getDescription、cost方法即可,实现了高扩展,解决了ifelse low爆炸的问题。
1、基础电脑组件
/ * 基础电脑 */ public class BasicComputer extends Computer { @Override public String getDescription() { return "Basic Computer"; } @Override public double cost() { return 500; // 基础电脑的价格 } }
2、抽象组件
package com.guor.designPattern.decorator; / * 抽象组件 */ public abstract class Computer { // 描述 public abstract String getDescription(); // 成本 public abstract double cost(); }
3、抽象装饰器
/ * 抽象装饰器 */ public abstract class ComputerDecorator extends Computer { protected Computer computer; public ComputerDecorator(Computer computer) { this.computer = computer; } public abstract String getDescription(); }
4、具体装饰器
public class StorageUpgrade extends ComputerDecorator { public StorageUpgrade(Computer computer) { super(computer); } @Override public String getDescription() { return computer.getDescription() + ", with 1TB SSD"; } @Override public double cost() { return computer.cost() + 200; } } public class RAMUpgrade extends ComputerDecorator { public RAMUpgrade(Computer computer) { super(computer); } @Override public String getDescription() { return computer.getDescription() + ", with 16GB RAM"; } @Override public double cost() { return computer.cost() + 150; // 额外内存的价格 } } public class GPUUpgrade extends ComputerDecorator { public GPUUpgrade(Computer computer) { super(computer); } @Override public String getDescription() { return computer.getDescription() + ", with NVIDIA GPU"; } @Override public double cost() { return computer.cost() + 300; // 额外显卡的价格 } }
四、使用装饰器模式有哪些优势
1、动态扩展对象功能
装饰器模式允许在运行时动态地为对象添加功能,而不需要修改对象的结构。相比于继承,它提供了更为灵活的扩展方式,因为继承在编译时就固定了对象的行为,而装饰器则可以通过组合多个装饰器动态改变对象的行为。
2、更好的代码复用性
装饰器可以封装可复用的功能,不同的对象可以共享这些功能而不需要重复编写代码。这种方式提高了代码的可维护性和复用性。
3、遵循单一职责原则
装饰器将对象的核心职责和其他辅助功能(如日志、缓存、权限控制)分离开来,每个装饰器只负责一个特定的功能,避免了类的职责过多。
4、减少子类的数量
继承机制常常会导致大量的子类,因为每个新的功能都需要通过子类来实现。而装饰器模式可以通过组合多个装饰器来实现相同的功能扩展,减少类的数量和复杂度。
5、运行时灵活性
装饰器可以在运行时动态地添加或移除功能,而不需要修改已有的代码。这在开发过程中非常实用,尤其是在需要频繁调整功能的场景中。
五、哪些场景可以使用装饰器模式优化?
1、增强对象功能时
当需要为现有对象增加新功能,且不希望修改对象本身或者使用继承时,可以考虑使用装饰器模式。比如在 UI 组件系统中,基础组件可以通过装饰器动态添加滚动、边框等特性,而不需要修改原始组件的代码。
2、跨领域关注点(如日志、权限验证、性能监控)
在处理一些横切关注点(cross-cutting concerns)时,装饰器模式非常有用。比如日志记录、权限控制、事务管理、性能监控等功能,它们常常不属于对象的核心业务逻辑,通过装饰器模式可以将这些功能单独提取出来,并动态添加到业务逻辑中,而不污染原始代码。
示例:
- 日志记录:为方法调用自动添加日志功能,记录方法的输入、输出和执行时间。
- 权限控制:在执行某些业务方法前,动态检查用户权限。
- 事务管理:在数据库操作前后动态添加事务处理。
3、需要多个功能组合时
当对象的功能可以通过不同的方式组合时,装饰器模式是一个理想的选择。它允许通过多个装饰器的组合形成不同的功能,比如装饰器可以为对象依次添加不同的行为,从而形成灵活的功能组合。
示例:在咖啡订单系统中,咖啡基础类可以通过装饰器动态添加牛奶、糖、巧克力等配料,而不用创建大量的子类来表示每一种咖啡组合。
4、避免类爆炸(class explosion)
通过装饰器模式可以避免通过继承产生大量的子类。特别是在当类的功能是通过多个维度扩展时,比如一种基础功能的类可能同时需要加日志、加缓存、加权限控制,使用继承将导致很多子类,而使用装饰器可以有效减少类的数量。
5、与策略模式配合使用
装饰器模式可以与策略模式结合起来,为策略类提供更丰富的功能扩展。比如在电商网站中,可以为订单结算策略类动态添加打折、积分计算等功能,而不必为每种结算方式都创建新的子类。
六、优化场景举例
场景 1:Web 应用中的权限验证和日志记录
在 Web 应用中,通常需要在处理请求时添加权限验证和日志记录。假设系统已经有一个基本的请求处理类,通过装饰器可以实现动态添加权限验证和日志记录功能。
在这个场景中,通过装饰器,我们可以动态地为请求处理类添加权限验证和日志记录功能,而不必修改现有的 RequestHandler 类。
// 定义一个处理请求的接口 interface RequestHandler { void handle(String request); } // 具体的请求处理类 class BasicRequestHandler implements RequestHandler { @Override public void handle(String request) { System.out.println("Handling request: " + request); } } // 抽象装饰器类 abstract class RequestHandlerDecorator implements RequestHandler { protected RequestHandler handler; public RequestHandlerDecorator(RequestHandler handler) { this.handler = handler; } @Override public void handle(String request) { handler.handle(request); } } // 日志记录装饰器 class LoggingDecorator extends RequestHandlerDecorator { public LoggingDecorator(RequestHandler handler) { super(handler); } @Override public void handle(String request) { System.out.println("Logging request: " + request); super.handle(request); } } // 权限验证装饰器 class AuthDecorator extends RequestHandlerDecorator { public AuthDecorator(RequestHandler handler) { super(handler); } @Override public void handle(String request) { if (checkPermission(request)) { System.out.println("Permission granted"); super.handle(request); } else { System.out.println("Permission denied"); } } private boolean checkPermission(String request) { // 简单模拟权限验证逻辑 return request.contains("admin"); } } // 测试客户端 public class DecoratorExample { public static void main(String[] args) { RequestHandler handler = new BasicRequestHandler(); RequestHandler authHandler = new AuthDecorator(handler); RequestHandler loggingAuthHandler = new LoggingDecorator(authHandler); // 测试通过权限验证和日志记录的请求处理 loggingAuthHandler.handle("admin: important data"); } }
场景 2:电子商务系统中的商品定价策略
假设一个电子商务系统中,需要对商品的价格进行灵活调整。可以通过装饰器模式为商品价格添加不同的优惠策略,例如打折、满减、会员优惠等。
在此例中,通过装饰器可以灵活组合打折和满减的策略,而不需要为每种优惠策略创建新的子类,简化了系统结构。
// 商品接口 interface Product { double getPrice(); } // 基础商品类 class BasicProduct implements Product { private double price; public BasicProduct(double price) { this.price = price; } @Override public double getPrice() { return price; } } // 抽象装饰器类 abstract class ProductDecorator implements Product { protected Product product; public ProductDecorator(Product product) { this.product = product; } @Override public double getPrice() { return product.getPrice(); } } // 打折装饰器 class DiscountDecorator extends ProductDecorator { public DiscountDecorator(Product product) { super(product); } @Override public double getPrice() { return product.getPrice() * 0.9; // 10% 折扣 } } // 满减装饰器 class FullReductionDecorator extends ProductDecorator { public FullReductionDecorator(Product product) { super(product); } @Override public double getPrice() { double price = product.getPrice(); if (price > 200) { return price - 20; // 满200减20 } return price; } } // 测试客户端 public class EcommerceExample { public static void main(String[] args) { Product product = new BasicProduct(250); Product discountProduct = new DiscountDecorator(product); Product finalProduct = new FullReductionDecorator(discountProduct); System.out.println("Final price: " + finalProduct.getPrice()); } }
装饰器模式在需要动态扩展对象功能、复用功能模块、组合不同行为以及避免类爆炸的场景中非常有用。它可以通过装饰器类灵活地为对象添加职责,从而提升代码的可维护性、复用性和扩展性。
七、典型案例:Java I/O 流中的装饰器模式
在JDK源码中,装饰器模式有很多应用,特别是在I/O类库中,装饰器模式被广泛使用。通过装饰器模式,Java的I/O类库可以动态地为输入输出流添加缓冲、数据处理等功能,而不改变原始的输入输出流的实现。
在Java中,java.io包的输入输出流(InputStream、OutputStream)就是装饰器模式的一个典型应用。InputStream和OutputStream类是抽象基类,具体的实现类如FileInputStream、BufferedInputStream等都是具体组件,而BufferedInputStream、DataInputStream等类则是装饰器,它们通过封装InputStream实现了额外的功能,比如缓冲和数据类型处理。
1、BufferedInputStream 是 InputStream 的装饰器
// java.io.BufferedInputStream 源码片段 public class BufferedInputStream extends FilterInputStream { // Buffer size protected volatile byte[] buf; public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } public BufferedInputStream(InputStream in, int size) { super(in); // 调用父类 FilterInputStream 的构造方法,传入被装饰的 InputStream if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } @Override public int read() throws IOException { // 通过缓存读取流数据,添加缓冲功能 } // 其他相关的重写方法 }
FilterInputStream:抽象装饰器类,它继承了InputStream,并且持有一个InputStream的引用,装饰器类通过持有的这个引用来增强对象的功能。
BufferedInputStream:具体的装饰器类,通过为InputStream添加缓冲功能提高读取效率。
public class FileInputStream extends InputStream { // 打开文件并创建文件输入流 public FileInputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null); } // 其他相关代码... } // 客户端使用 public class DecoratorTest { public static void main(String[] args) throws IOException { InputStream fileStream = new FileInputStream("data.txt"); // 用 BufferedInputStream 装饰 FileInputStream,增加缓冲功能 InputStream bufferedStream = new BufferedInputStream(fileStream); int data = bufferedStream.read(); // 读取数据 while (data != -1) { System.out.print((char) data); data = bufferedStream.read(); } bufferedStream.close(); } }
在这个例子中,FileInputStream是一个具体的输入流,而BufferedInputStream是装饰器,增加了缓冲功能。客户端可以通过组合来增强输入流的功能,而不必修改原始的FileInputStream类。
2、DataInputStream 是 InputStream 的装饰器
DataInputStream 是另一个经典的装饰器,它允许从输入流中读取基本数据类型(如int、float等)。
// java.io.DataInputStream 源码片段 public class DataInputStream extends FilterInputStream implements DataInput { public DataInputStream(InputStream in) { super(in); // 调用装饰的 InputStream } // 读取一个 int 数据 public final int readInt() throws IOException { int ch1 = in.read(); int ch2 = in.read(); int ch3 = in.read(); int ch4 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } // 其他相关的读取方法 }
在这个例子中,DataInputStream装饰了一个InputStream,并且扩展了功能,支持读取基本数据类型。
public class DecoratorTest { public static void main(String[] args) throws IOException { InputStream fileStream = new FileInputStream("data.bin"); // 使用 BufferedInputStream 增加缓冲功能 InputStream bufferedStream = new BufferedInputStream(fileStream); // 使用 DataInputStream 增加读取基本数据类型的功能 DataInputStream dataStream = new DataInputStream(bufferedStream); int number = dataStream.readInt(); // 从文件中读取一个 int 值 System.out.println("Read integer: " + number); dataStream.close(); } }
在这个例子中,DataInputStream为InputStream增加了读取基本数据类型的功能。通过使用BufferedInputStream和DataInputStream的组合,客户端可以获得既有缓冲功能,又可以读取基本数据类型的输入流。
3、PrintStream 和 PrintWriter 也是装饰器
PrintStream 和 PrintWriter 是对输出流的装饰器,它们添加了打印方法,比如print()和println(),使得输出流可以方便地输出字符串、对象等数据。
public class PrintStream extends FilterOutputStream implements Appendable, Closeable { // 构造函数接受 OutputStream 作为参数 public PrintStream(OutputStream out) { super(out); // 调用 FilterOutputStream 构造方法 } public void println(String x) { synchronized (this) { print(x); newLine(); } } // 其他相关代码... }
在Java I/O类库中,装饰器模式通过扩展InputStream和OutputStream的功能而被广泛使用。
BufferedInputStream、DataInputStream、PrintStream等类都是装饰器,它们在不改变底层流的情况下增加了缓冲、数据类型处理和打印等功能。
通过装饰器模式,Java I/O类库实现了灵活的功能扩展,并且使用者可以根据需求动态组合这些装饰器。
4、除了I/O流之外,Java中的装饰器模式也被应用在其他场景中:
(1)Java的集合类包装器
Collections.synchronizedList()、Collections.unmodifiableList()等方法实际上是对集合进行包装,动态为集合对象增加线程安全或不可修改的特性。
(2)java.util.logging.Logger
日志框架中的Logger类允许通过装饰器为日志添加各种处理行为,例如格式化输出、发送到不同的目的地(控制台、文件、远程服务器等)。
八、总结
装饰器模式在JDK源码中的典型应用主要体现在I/O流的设计中。通过装饰器模式,Java的InputStream和OutputStream类可以在不修改原始类的情况下动态扩展功能。例如,BufferedInputStream和DataInputStream分别为基础流增加了缓冲读取和读取基本数据类型的能力,而PrintStream为输出流提供了打印字符串、对象等的便捷方法。
装饰器模式允许开发者通过组合多个装饰器类,为对象添加不同的功能,避免了创建大量子类的复杂性。例如,BufferedInputStream可以与FileInputStream组合使用,为文件输入流增加缓冲,提升读取效率。这种模式不仅灵活,还使代码复用性和扩展性大幅提升。
此外,装饰器模式还广泛应用于Java集合类和日志系统等场景,像Collections.synchronizedList()、Collections.unmodifiableList()等都通过装饰器动态增加线程安全或不可修改的特性。装饰器模式通过其灵活的结构设计,实现了功能增强与解耦的目的,是Java开发中的常见设计模式。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/124337.html