当我们想对一个类进行功能扩展的时候,最简单的方法就是继承该类然后进行修改,但是一个接口下的实现类很多,每个子类都进行继承扩展的话又会诞生很多子类,造成类爆炸的情况。

装饰器模式属于结构型设计模式,就可以做到在不改变原代码的情况下完成功能的扩展,符合OCP原则,也避免了类爆炸的情况。

Java中的IO流大量使用了装饰器模式,可以参考官方的源码进行学习。

  • 在装饰器设计中,两个重要角色:装饰者与被装饰者

  • 装饰器设计模式中,要求装饰者与被装饰者应实现同一个接口/同一些接口/继承同一个抽象类

  • 因为实现同一个接口以后,对于客户端程序来说,使用装饰者时就像在使用被装饰者一样

  • 装饰者中含有被装饰者的引用(A has a B),尽量使用has a[耦合度低一些],不要使用is a(即组合优于继承原则)

例如我们想实现一个自己的Set,能够记录下被移除的元素,有些人可能会想到继承个set,但是我们需要注意,继承只能单继承,这样操作会使该类失去其他继承的资格,所以优先还是实现set接口而不影响原本的继承扩展。

-HistorySet.java

package insight;

import java.util.*;

/**
 * @author: wangcai
 * @date: 2025/12/15
 */
public class HistorySet<E> implements Set<E> {

    /* 记录Remove掉的元素 */
    List<E> removeList = new ArrayList<>();

    final Set<E> delegate;

    public HistorySet(Set<E> delegate) {
        this.delegate = delegate;
    }

    @Override
    public int size() {
        return delegate.size();
    }

    @Override
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return delegate.contains(o);
    }

    @Override
    public Iterator iterator() {
        return delegate.iterator();
    }

    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @Override
    public boolean add(Object o) {
        return delegate.add((E) o);
    }

    @Override
    public boolean remove(Object o) {
        boolean remove = delegate.remove(o);
        if (remove) {
            removeList.add((E) o);
        }
        return remove;
    }

    @Override
    public boolean addAll(Collection c) {
        return false;
    }

    @Override
    public void clear() {

    }

    @Override
    public boolean removeAll(Collection c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection c) {
        return false;
    }

    @Override
    public boolean containsAll(Collection c) {
        return false;
    }

    @Override
    public Object[] toArray(Object[] a) {
        return new Object[0];
    }

    @Override
    public String toString() {
        return "HistorySet{ " +
                "delegate = " + delegate.toString() + " " +
                "removeList = " + removeList.toString() +
                " }";
    }
}

例如上面这样,这样可以自己无需实现set的功能就可以具备set的能力。而且支持手动传入set实例,由用户自己控制想要扩展的set,HashSet或者TreeSet。而如果通过继承实现该扩展功能的话,就不能这么灵活,我们需要每种实现都单独编写一个扩展类。

-Usage.java

package insight;

import java.util.HashSet;

/**
 * @author: wangcai
 * @date: 2025/12/15
 */
public class Usage {
    public static void main(String[] args) {
        HistorySet<Integer> integers = new HistorySet<Integer>(new HashSet<>());
        integers.add(1);
        integers.add(2);
        integers.add(3);
        integers.add(4);
        integers.add(5);
        integers.remove(2);
        integers.remove(2);
        integers.remove(5);
        System.out.println(integers);
    }
}
/*
HistorySet{ delegate = [1, 3, 4] removeList = [2, 5] }
*/

并且我们可以反复的包,因为我们实现的本身也是一个set集合,还是能作为构造器参数传入。

package insight;

import java.util.HashSet;

/**
 * @author: wangcai
 * @date: 2025/12/15
 */
public class Usage {
    public static void main(String[] args) {
        HistorySet<Integer> integers = new HistorySet<>(new HashSet<>());
        HistorySet<Integer> integers1 = new HistorySet<>(integers);
        integers1.add(1);
        integers1.add(2);
        integers1.add(3);
        integers1.add(4);
        integers1.add(5);
        integers1.remove(2);
        integers1.remove(2);
        integers1.remove(5);
        System.out.println(integers1);
    }
}
/*
HistorySet{ delegate = HistorySet{ delegate = [1, 3, 4] removeList = [2, 5] } removeList = [2, 5] }
*/

我们可以看一下Java中的一些实现:

Collections.synchronizedCollection()

public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
}
image-20251216095755442

=== BufferedInputStream

我们自己手写一个BufferedInputStream模仿Java IO流中的缓冲输入流来提升读取效率。

-Usage.java

package insight.input;

import java.io.File;
import java.io.FileInputStream;
import java.time.Instant;

/**
 * @author: wangcai
 * @date: 2025/12/16
 */
public class Usage {
    public static void main(String[] args) throws Exception {
        File file = new File("src/main/resources/深入理解Java虚拟机(第3版).pdf");
        long millisecond = Instant.now().toEpochMilli();
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            while (true) {
                int read = fileInputStream.read();
                if (read == -1) {
                    break;
                }
            }
        }
        System.out.println("耗时: " + (Instant.now().toEpochMilli() - millisecond) + "ms");
    }
}
/*
耗时: 62638ms
*/

可以看的出来,耗时很久,基于此我们利用装饰器模式自己改造一下FileInputStream。缓冲区的核心在于我们需要读取的时候保证效率高,但是又不需要一口气读完,所以我们需要创建一个缓冲区读取文件了保存下缓存内容,需要的时候在内存中直接拿取。

-BufferedFileInputStream.java

package insight.input;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author: wangcai
 * @date: 2025/12/16
 */
public class BufferedFileInputStream extends InputStream {

    private final InputStream in;

    // 缓冲区 默认8kb
    byte[] buffer = new byte[8192];

    // 标志位 为-1时代表不可读 其余为读取的开始位置
    private int bufferPos = -1;

    // 用来避免超出的读取文件
    private int capacity = -1;

    public BufferedFileInputStream(InputStream inputStream) {
        this.in = inputStream;
    }

    @Override
    public int read() throws IOException {
        if (buffCanRead()) {
            return readFromBuffer();
        }
        refreshBuffer();
        if (!buffCanRead()) {
            return -1;
        }
        return readFromBuffer();
    }

    private int readFromBuffer() {
        return buffer[bufferPos++] & 0xff;
    }

    private void refreshBuffer() throws IOException {
        capacity = this.in.read(buffer);
        bufferPos = 0;
    }

    private boolean buffCanRead() {
        if (capacity == -1) {
            return false;
        }
        return bufferPos != capacity;
    }

    @Override
    public void close() throws IOException {
        super.close();
    }
}
package insight.input;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.time.Instant;

/**
 * @author: wangcai
 * @date: 2025/12/16
 */
public class Usage {
    public static void main(String[] args) throws Exception {
        File file = new File("src/main/resources/深入理解Java虚拟机(第3版).pdf");
        long millisecond = Instant.now().toEpochMilli();
        try (InputStream fileInputStream = new BufferedFileInputStream(new FileInputStream(file))) {
            while (true) {
                int read = fileInputStream.read();
                if (read == -1) {
                    break;
                }
            }
        }
        System.out.println("耗时: " + (Instant.now().toEpochMilli() - millisecond) + "ms");
    }
}
/*
耗时: 49ms
*/

效率大大提升,这也是官方源码中类似的用法的实践。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐