重学设计模式,【结构型】装饰器模式

在日常开发中,我们往往忽视了设计模式的重要性。这可能是因为项目时间紧迫,或者对设计模式理解不深。其实,很多时候我们可能在不经意间已经使用了某些模式。

重要的是要有意识地学习和应用,让代码更加优雅和高效。也许是时候重新审视我们的编程实践,将设计模式融入其中了。

今天由浅入深,重学【装饰器模式】,让我们一起"重学设计模式"。

装饰器模式(Decorator Pattern)是一种结构型设计模式,允许通过一种灵活的方式动态地为对象添加新的行为或职责,而无需修改其原始代码。这种模式提供了比继承更有弹性的替代方案,通过将对象包装在一系列装饰器类中,可以在运行时组合出各种行为。

一、装饰器模式的结构

  1. Component(组件):定义一个对象接口,装饰器和被装饰对象必须实现的公共接口。
  2. ConcreteComponent(具体组件):实现Component接口的类,是可以被装饰的类。
  3. Decorator(装饰器):实现Component接口,同时持有Component类型的成员变量(可以是原始对象或其他装饰器),通过组合将功能动态扩展到该对象。
  4. ConcreteDecorator(具体装饰器):实现装饰器具体行为扩展的类,通过调用被装饰对象的行为,并在此基础上添加新的功能。

二、不使用装饰器模式时

假设我们有一个基本的电脑类,并想为它动态添加不同的配置(比如CPU、内存、GPU)。

当需要新增配置时,在Computer类中新增即可,清晰明了,没有一个ifelse解决不了的。

java 复制代码
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 $" + myComputer.cost());
    }
}

三、使用装饰器模式优化

当需要新增配置时,直接实现ComputerDecorator,重写getDescription、cost方法即可,实现了高扩展,解决了ifelse low爆炸的问题。

1、基础电脑组件

java 复制代码
/**
 * 基础电脑
 */
public class BasicComputer extends Computer {

    @Override
    public String getDescription() {
        return "Basic Computer";
    }

    @Override
    public double cost() {
        return 500;  // 基础电脑的价格
    }
}

2、抽象组件

java 复制代码
package com.guor.designPattern.decorator;

/**
 * 抽象组件
 */
public abstract class Computer {

    // 描述
    public abstract String getDescription();

    // 成本
    public abstract double cost();
}

3、抽象装饰器

java 复制代码
/**
 * 抽象装饰器
 */
public abstract class ComputerDecorator extends Computer {

    protected Computer computer;

    public ComputerDecorator(Computer computer) {
        this.computer = computer;
    }

    public abstract String getDescription();
}

4、具体装饰器

java 复制代码
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)时,装饰器模式非常有用。比如日志记录、权限控制、事务管理、性能监控等功能,它们常常不属于对象的核心业务逻辑,通过装饰器模式可以将这些功能单独提取出来,并动态添加到业务逻辑中,而不污染原始代码。

示例:

  1. 日志记录:为方法调用自动添加日志功能,记录方法的输入、输出和执行时间。
  2. 权限控制:在执行某些业务方法前,动态检查用户权限。
  3. 事务管理:在数据库操作前后动态添加事务处理。

3、需要多个功能组合时

当对象的功能可以通过不同的方式组合时,装饰器模式是一个理想的选择。它允许通过多个装饰器的组合形成不同的功能,比如装饰器可以为对象依次添加不同的行为,从而形成灵活的功能组合。

示例:在咖啡订单系统中,咖啡基础类可以通过装饰器动态添加牛奶、糖、巧克力等配料,而不用创建大量的子类来表示每一种咖啡组合。

4、避免类爆炸(class explosion)

通过装饰器模式可以避免通过继承产生大量的子类。特别是在当类的功能是通过多个维度扩展时,比如一种基础功能的类可能同时需要加日志、加缓存、加权限控制,使用继承将导致很多子类,而使用装饰器可以有效减少类的数量。

5、与策略模式配合使用

装饰器模式可以与策略模式结合起来,为策略类提供更丰富的功能扩展。比如在电商网站中,可以为订单结算策略类动态添加打折、积分计算等功能,而不必为每种结算方式都创建新的子类。

六、优化场景举例

场景 1:Web 应用中的权限验证和日志记录

在 Web 应用中,通常需要在处理请求时添加权限验证和日志记录。假设系统已经有一个基本的请求处理类,通过装饰器可以实现动态添加权限验证和日志记录功能。

在这个场景中,通过装饰器,我们可以动态地为请求处理类添加权限验证和日志记录功能,而不必修改现有的 RequestHandler 类。

java 复制代码
// 定义一个处理请求的接口
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:电子商务系统中的商品定价策略

假设一个电子商务系统中,需要对商品的价格进行灵活调整。可以通过装饰器模式为商品价格添加不同的优惠策略,例如打折、满减、会员优惠等。

在此例中,通过装饰器可以灵活组合打折和满减的策略,而不需要为每种优惠策略创建新的子类,简化了系统结构。

java 复制代码
// 商品接口
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 复制代码
// 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添加缓冲功能提高读取效率。

java 复制代码
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 复制代码
// 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,并且扩展了功能,支持读取基本数据类型。

java 复制代码
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(),使得输出流可以方便地输出字符串、对象等数据。

java 复制代码
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开发中的常见设计模式。

相关推荐
rzl029 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习14 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl28 分钟前
深度解读jdk8 HashMap设计与源码
java
Falling4232 分钟前
使用 CNB 构建并部署maven项目
后端
guojl33 分钟前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假42 分钟前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
A~taoker1 小时前
taoker的项目维护(ng服务器)
java·开发语言
萧曵 丶1 小时前
Rust 中的返回类型
开发语言·后端·rust