Guava Forwarding系列类详解——装饰器模式实战

大家好,我是桦说编程。

本文通过 ForwardingList 深入解析 Guava 的 ForwardingXXX 系列类,掌握装饰器模式在集合扩展中的优雅实现。Fowarding系列类广义上涉及三种设计模式:装饰器模式、模版方法模式、委托模式。本文由我辅助AI编写。

问题背景

在实际开发中,我们经常需要对标准集合类进行功能增强:

  • 在 List 添加元素时自动记录日志
  • 对 Map 的 put 操作进行参数校验
  • 在 Set 的读写操作上添加性能埋点

传统做法是直接继承 ArrayList、HashMap,但这会遇到几个问题:

  1. 需要重写大量方法:List 接口有 30+ 个方法,逐个重写工作量大
  2. 容易遗漏:某些方法忘记重写会导致增强逻辑失效
  3. 方法委托关系复杂 :比如 addAll 内部可能调用 add,重写时需要理解内部实现

Guava 的 ForwardingXXX 系列正是为解决这类问题而设计。

核心原理

装饰器模式回顾

sql 复制代码
┌─────────────────────────────────────────────────┐
│                   <<interface>>                 │
│                       List                      │
├─────────────────────────────────────────────────┤
│  + add(E): boolean                              │
│  + get(int): E                                  │
│  + size(): int                                  │
│  ...                                            │
└─────────────────────────────────────────────────┘
                        ▲
                        │ implements
          ┌─────────────┴─────────────┐
          │                           │
┌─────────────────┐         ┌─────────────────────┐
│    ArrayList    │         │   ForwardingList    │
├─────────────────┤         ├─────────────────────┤
│ 真正的实现        │◄────────│ - delegate: List<E> │
└─────────────────┘   委托   │ + delegate(): List  │
                            │ + add(E): boolean   │
                            │   → delegate.add(e) │
                            └─────────────────────┘
                                      ▲
                                      │ extends
                            ┌─────────────────────┐
                            │   LoggingList<E>    │
                            ├─────────────────────┤
                            │ 只重写需要增强的方法 │
                            │ + add(E): boolean   │
                            │   → log + super.add │
                            └─────────────────────┘

ForwardingList 源码剖析

java 复制代码
@GwtCompatible
public abstract class ForwardingList<E> extends ForwardingCollection<E> implements List<E> {

    protected ForwardingList() {}

    // 子类必须实现:返回被装饰的 List
    @Override
    protected abstract List<E> delegate();

    // 所有方法都委托给 delegate()
    @Override
    public void add(int index, E element) {
        delegate().add(index, element);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> elements) {
        return delegate().addAll(index, elements);
    }

    @Override
    public E get(int index) {
        return delegate().get(index);
    }

    @Override
    public int indexOf(Object element) {
        return delegate().indexOf(element);
    }

    // ... 其他 List 方法同样委托

    // 标准方法实现:基于其他方法的默认实现
    protected boolean standardAdd(E element) {
        add(size(), element);
        return true;
    }

    protected boolean standardAddAll(int index, Iterable<? extends E> elements) {
        return Lists.addAllImpl(this, index, elements);
    }

    // ... 更多 standardXxx 方法
}

关键设计点

设计元素 说明
abstract delegate() 强制子类指定被装饰对象,延迟绑定
所有方法委托 默认行为透传,子类按需重写
standardXxx 方法 提供基于基本操作的默认实现,避免委托循环

standardXxx 方法的作用

这是 ForwardingXXX 的精华所在。考虑这个场景:

java 复制代码
// 错误示范:addAll 内部可能直接调用 delegate 的 addAll
public class LoggingList<E> extends ForwardingList<E> {
    @Override
    public boolean add(int index, E element) {
        log.info("Adding: {}", element);
        return delegate().add(index, element);
    }

    // addAll 没重写,直接委托给 delegate.addAll()
    // 结果:addAll 添加的元素不会被日志记录!
}

正确做法是使用 standardAddAll

java 复制代码
@Override
public boolean addAll(int index, Collection<? extends E> elements) {
    return standardAddAll(index, elements); // 内部循环调用 this.add()
}

实战示例

示例1:带日志的 List

java 复制代码
public class LoggingList<E> extends ForwardingList<E> {
    private static final Logger log = LoggerFactory.getLogger(LoggingList.class);
    private final List<E> delegate;

    public LoggingList(List<E> delegate) {
        this.delegate = delegate;
    }

    @Override
    protected List<E> delegate() {
        return delegate;
    }

    @Override
    public boolean add(E element) {
        log.info("[ADD] element={}", element);
        return standardAdd(element);
    }

    @Override
    public void add(int index, E element) {
        log.info("[ADD] index={}, element={}", index, element);
        delegate().add(index, element);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> elements) {
        log.info("[ADD_ALL] index={}, size={}", index, elements.size());
        return standardAddAll(index, elements); // 会逐个调用 add,每个都有日志
    }

    @Override
    public E remove(int index) {
        E removed = delegate().remove(index);
        log.info("[REMOVE] index={}, element={}", index, removed);
        return removed;
    }
}

示例2:带容量限制的 List

java 复制代码
public class BoundedList<E> extends ForwardingList<E> {
    private final List<E> delegate;
    private final int maxSize;

    public BoundedList(List<E> delegate, int maxSize) {
        this.delegate = delegate;
        this.maxSize = maxSize;
    }

    @Override
    protected List<E> delegate() {
        return delegate;
    }

    private void checkCapacity(int additionalElements) {
        if (size() + additionalElements > maxSize) {
            throw new IllegalStateException(
                "Exceeds max size: " + maxSize + ", current: " + size());
        }
    }

    @Override
    public boolean add(E element) {
        checkCapacity(1);
        return standardAdd(element);
    }

    @Override
    public void add(int index, E element) {
        checkCapacity(1);
        delegate().add(index, element);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> elements) {
        checkCapacity(elements.size());
        return delegate().addAll(index, elements);
    }
}

示例3:性能埋点 List

java 复制代码
public class MetricsList<E> extends ForwardingList<E> {
    private final List<E> delegate;
    private final AtomicLong readCount = new AtomicLong();
    private final AtomicLong writeCount = new AtomicLong();

    public MetricsList(List<E> delegate) {
        this.delegate = delegate;
    }

    @Override
    protected List<E> delegate() {
        return delegate;
    }

    @Override
    public E get(int index) {
        readCount.incrementAndGet();
        return delegate().get(index);
    }

    @Override
    public boolean add(E element) {
        writeCount.incrementAndGet();
        return standardAdd(element);
    }

    @Override
    public void add(int index, E element) {
        writeCount.incrementAndGet();
        delegate().add(index, element);
    }

    public long getReadCount() {
        return readCount.get();
    }

    public long getWriteCount() {
        return writeCount.get();
    }
}

ForwardingXXX 家族

Guava 为常用集合都提供了 Forwarding 包装类:

类名 对应接口 典型应用场景
ForwardingList List 有序集合增强
ForwardingSet Set 去重集合增强
ForwardingMap Map K-V 映射增强
ForwardingQueue Queue 队列增强
ForwardingMultimap Multimap 一对多映射增强
ForwardingTable Table 二维表增强
ForwardingIterator Iterator 迭代器增强
ForwardingConcurrentMap ConcurrentMap 并发 Map 增强

与直接继承对比

方面 直接继承 ArrayList ForwardingList
代码量 重写所有需要增强的方法 只重写需要增强的方法
灵活性 绑定特定实现 可装饰任意 List 实现
组合能力 单一继承 可多层装饰叠加
实现替换 需修改代码 构造时注入即可

装饰器叠加示例

java 复制代码
// 一个 List 同时具备日志 + 容量限制 + 埋点
List<String> base = new ArrayList<>();
List<String> bounded = new BoundedList<>(base, 100);
List<String> logged = new LoggingList<>(bounded);
List<String> metered = new MetricsList<>(logged);

// 调用顺序:metered → logged → bounded → base
metered.add("item");

总结

  • ForwardingXXX 是装饰器模式的标准实现 :通过 delegate() 委托,只重写需要增强的方法
  • standardXxx 方法避免委托循环:当增强逻辑依赖基础操作时,使用 standard 方法确保调用链正确
  • 支持多层装饰叠加:日志、校验、埋点等可以自由组合
  • Guava 提供全系列 Forwarding 类:List、Set、Map、Queue、Multimap、Table 等都有对应实现

如果这篇文章对你有帮助,欢迎关注我,持续分享高质量技术干货,助你更快提升编程能力。

相关推荐
老神在在00110 分钟前
Spring Bean 的六种作用域详解
java·后端·spring
仙草不加料12 分钟前
互联网大厂Java面试故事实录:三轮场景化技术提问与详细答案解析
java·spring boot·微服务·面试·aigc·电商·内容社区
程序员老邢17 分钟前
【技术底稿 19】Redis7 集群密码配置 + 权限锁死 + 磁盘占满连锁故障真实排查全记录
java·服务器·经验分享·redis·程序人生·微服务
落魄江湖行39 分钟前
基础篇一 Java 有了 int 为什么还要 Integer?它们到底差在哪?
java·面试·八股文
星辰_mya1 小时前
OSI 七层模型之“跨国诈骗集团”深度讲解
运维·服务器·后端·面试·架构师
IT_陈寒1 小时前
SpringBoot自动配置这破玩意儿又坑我一次
前端·人工智能·后端
LiLiYuan.2 小时前
【Java 6种线程状态】
java·开发语言
itzixiao2 小时前
L1-047 装睡 (5分)[java][python]
java·开发语言·python
码事漫谈2 小时前
Cursor+Graphify实属强强联合了
后端
用户298698530142 小时前
不用无头浏览器,Java 如何将 HTML 转成图片?
java·后端