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

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

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

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

装饰器模式(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开发中的常见设计模式。

相关推荐
手握风云-2 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
许苑向上5 分钟前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
喵叔哟22 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生28 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
闲人一枚(学习中)35 分钟前
设计模式-创建型-抽象工厂模式
设计模式·抽象工厂模式
郑祎亦1 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
不是二师兄的八戒1 小时前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
爱编程的小生1 小时前
Easyexcel(2-文件读取)
java·excel
本当迷ya1 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法