装饰者模式Decorator

简单版

防止类爆炸

类继承是会造成耦合度很高

用聚合代替继承,和代理不一样的是主动权在谁手里,聚合是主动的,自己知道的,代理是被动,自己甚至不知道

  • Component:抽象被装饰组件,定义都有哪些功能。
  • ConcreteComponent:抽象被装饰组件实现类
  • Decorator:抽象****装饰器,不一定是接口,它持有一个Component对象实例的引用,定义一个与抽象被装饰组件一致的接口;
  • ConcreteDecorator:具体****装饰器,负责实现装饰器角色定义的功能。

详细版

我们在做一个产品的时候,可能并没有考虑到新需求的场景,此时就需要为某些组件添加新的功能来满足这些需求。

  • 如果要符合开放-封闭的原则,我们最好不要直接修改已有的具体实现类,因为会破坏其已有的稳定性
  • 继承,在某些场景下是不可行的,例如,要覆盖的方法被 final 关键字修饰了,那么在 Java 的语法中就无法被覆盖。使用继承方案的另一个缺点就是整个继承树的膨胀,例如,当新需求存在多种排列组合或是复杂的判断时,那就需要写非常多的子类实现。
  • 应该尽量多地使用组合方式进行扩展,尽量少使用继承方式进行扩展,除非迫不得已。装饰器模式就是一种通过组合方式实现扩展的设计模式,它可以完美地解决上述功能增强的问题装饰器的核心思想是为已有实现类创建多个包装类,由这些新增的包装类完成新需求的扩展。相较于继承这种静态的扩展方式,装饰器模式可以在运行时根据系统状态,动态决定为一个实现类添加哪些扩展功能。

1622944590792-9b672e45-1c43-4054-9387-1228e501ed33.png

  • Component 接口:已有的业务接口,JDK 中的 IO 流体系就使用了装饰器模式,其中的 InputStream 接口就扮演了 Component 接口的角色。
  • ComponentImpl 实现类:实现了 Component 接口最基础、最核心的功能,也就是被装饰的、原始的基础类。在 JDK IO 流体系之中的 FileInputStream 就扮演了 ComponentImpl 的角色,它实现了读取文件的基本能力,例如,读取单个 byte、读取 byte[] 数组。
  • Decorator 抽象类:所有装饰器的父类,核心不是提供新的扩展能力,而是封装一个 Component 类型的字段,也就是被装饰的目标对象。
    • 这个被装饰的对象可以是 ComponentImpl 对象,也可以是 Decorator 实现类的对象,之所以这么设计,就是为了实现装饰器嵌套。这里的 DecoratorImpl1 装饰了 DecoratorImpl2,DecoratorImpl2 装饰了 ComponentImpl,经过了这一系列装饰之后得到的 Component 对象,除了具有 ComponentImpl 的基础能力之外,还拥有了 DecoratorImpl1 和 DecoratorImpl2 的扩展能力。JDK IO 流体系中的 FilterInputStream 就扮演了 Decorator 的角色。1622944718681-bbbcdd69-988e-4f89-8261-3776becd0939.png
    • Decorator 与 Component 的引用关系:Decorator 的具体实现类核心就是在被装饰对象的基础之上添加新的扩展功能。在 JDK IO 流体系中的 BufferedInputStream 就扮演了 DecoratorImpl 的角色,它在原有的 InputStream 基础上,添加了一个 byte[] 缓冲区,提供了更加高效的读文件操作。

应用InputStream,OutputStream,Reader,Writer

在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器可以 对另一些流处理器起到装饰作用,形成新的,具有改善了的功能的流处理器。

1616822818214-4251b30d-7023-453d-a806-7616a86d5be7.png

FilterInputStream继承了InputStream,也引用了InputStream,而它有四个子类,这就是所谓的Decorator模式。

BufferedInputStream“装饰”了InputStream的内部工作方式,使得流的读入操作使用了缓冲机制。在使用了缓冲机制后,不会对每一次的流读入操作都产生一个物理的读盘动作,从而提高了程序的效率,在汲及到物理流的读入时,都应当使用这个装饰流类。

package com.deltaqin.designPattern.d12_decorator;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @author deltaqin
 * @date 2021/3/27 1:38 下午
 */
public class Demo {

    public static void main(String[] args) throws FileNotFoundException,
            IOException {
        FileInputStream fis = null;
        
        // 不使用装饰器BufferedInputStream
        fis = new FileInputStream("./test.data");
        long t = System.currentTimeMillis();
        int c;
        while ((c = fis.read()) != -1) {
        }
        t = System.currentTimeMillis() - t;
        System.out.println("遍历文件用了如下时间:" + t);
        fis.close();
        
        // 使用装饰器BufferedInputStream
        FileInputStream fis1 = new FileInputStream("./test.data");
        BufferedInputStream bis = new BufferedInputStream(fis1);
        t = System.currentTimeMillis();

        while ((c = bis.read()) != -1) {}
        fis1.close();
        t = System.currentTimeMillis() - t;
        System.out.println("遍历文件用了如下时间:" + t);
        fis1.close();
        bis.close();
    }
}

LineNumberInputStream和PushbackInputStream也同样“装饰”了InputStream的内部工作方式,前者使得程序能够按照行号读入数据;后者能够使程序读入的过程中,退后一个字符。

Java语言的I/O库提供了四大等级结构: InputStream,OutputStream,Reader,Writer 四个系列的类。InputStream和OutputStream处理8位字节流数据, Reader和Writer处理16位的字符流数据。InputStream和Reader处理输入, OutputStream和Writer处理输出,所以OutputStream,Reader,Writer这三类的装饰模式跟前面详细介绍的InputStream装饰模式大同小异

1616823119832-34093fcd-d993-43c2-b31b-46e3c0710462.png

1616823220244-9054720f-8920-4bc6-a7f6-92a9301b9cdf.png

装饰者与适配器区别

适配器模式的意义是将一个接口转变成另一个接口,通过改变接口达到重复使用的目的;适配器****重点在于转换

装饰器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但增强原有对象的功能,或者改变原有对象的处理方法而提高性能。所有这两个模式的设计目的是不同的。装饰器****重点在于增强

posted on 2025-10-14 23:10  chuchengzhi  阅读(3)  评论(0)    收藏  举报

导航

杭州技术博主,专注分享云计算领域实战经验、技术教程与行业洞察, 打造聚焦云计算技术的垂直博客,助力开发者快速掌握云服务核心能力。

褚成志 云计算 技术博客