导读:本期聚焦于小伙伴创作的《如何用观察者模式实现可监听变量变动的自定义集合?》,敬请观看详情。把普通集合变成能感知内部元素增删改的对象,核心在于将“数据状态”与“状态变更通知”解耦。观察者模式通过定义主题与观察者接口,让集合在插入、删除或替换元素时主动推送事件。相比手动轮询集合内容,这种机制能降低模块耦合度并即时触发界面刷新或缓存同步。实现时需注意避免通知过程中的并发修改异常,并为监听器提供旧值与新值以支撑差异化处理。

在复杂前端或后端业务里,我们经常需要感知某个集合内部数据的变化,从而自动执行界面渲染、日志埋点或缓存更新。观察者模式提供了一种优雅的思路:把集合自身作为被观察的主题,把关心变化的逻辑封装成观察者,一旦集合发生变动就主动通知。下面以 Java 为例,从零构建一个具备变量变动监听能力的自定义集合。

如何用观察者模式实现可监听变量变动的自定义集合?

一、观察者模式的核心角色

观察者模式包含两种基本角色:主题(Subject)和观察者(Observer)。主题维护一份观察者列表,并在自身状态改变时遍历列表调用更新方法;观察者只依赖主题提供的通知接口,不需要主动查询数据。放到自定义集合场景里,集合就是主题,而监听集合变化的回调函数或对象就是观察者。

这种解耦带来的好处是明显的。假设页面有多个模块都依赖同一个用户列表,如果采用轮询方式,每个模块都要定时读取集合内容做比对,既浪费 CPU 又难保证实时性。而使用观察者模式后,集合在 add 或 remove 时立刻广播事件,相关模块各自响应,彼此互不知道对方存在,系统扩展性更强。

1.1 定义观察者接口

我们先抽象出一个通用的监听器接口,它接收集合变动的事件对象。事件里至少应包含操作类型、受影响的元素以及可选的旧值,这样观察者才能做精细化处理。

// 集合变更事件
class CollectionChangeEvent<E> {
    public enum Type { ADD, REMOVE, SET }
    private final Type type;
    private final E element;
    private final E oldValue;

    public CollectionChangeEvent(Type type, E element, E oldValue) {
        this.type = type;
        this.element = element;
        this.oldValue = oldValue;
    }
    public Type getType() { return type; }
    public E getElement() { return element; }
    public E getOldValue() { return oldValue; }
}

// 观察者接口
interface CollectionListener<E> {
    void onChanged(CollectionChangeEvent<E> event);
}

上述代码中,CollectionChangeEvent 作为数据载体,把“发生了什么”和“具体数据”打包传递给监听者。使用枚举 Type 区分新增、删除和替换,避免观察者用字符串判断导致拼写错误。

1.2 主题基类设计

主题负责注册、注销观察者,并在合适时机触发通知。我们把这部分逻辑抽出来,方便后续的具体集合类复用。

import java.util.ArrayList;
import java.util.List;

abstract class ObservableCollection<E> {
    private final List<CollectionListener<E>> listeners = new ArrayList<>();

    public void addListener(CollectionListener<E> l) {
        listeners.add(l);
    }

    public void removeListener(CollectionListener<E> l) {
        listeners.remove(l);
    }

    protected void fireEvent(CollectionChangeEvent.Type type, E element, E oldValue) {
        CollectionChangeEvent<E> evt = new CollectionChangeEvent<>(type, element, oldValue);
        // 使用副本遍历,防止监听器中移除自身导致并发修改异常
        for (CollectionListener<E> l : new ArrayList<>(listeners)) {
            l.onChanged(evt);
        }
    }
}

注意 fireEvent 方法里用 new ArrayList 拷贝了监听器列表再遍历。这是因为监听器在收到通知时可能会调用 removeListener 注销自己,如果直接遍历原列表会抛出 ConcurrentModificationException。这种细节在自定义集合里非常关键。

二、实现可监听的自定义列表

有了基类,我们可以写一个具体的 ObservableList,内部持有普通的 ArrayList,并包装常用方法。每次修改都先改底层数据,再发事件。

2.1 包装增删改方法

下面代码展示如何重写 add、remove 和 set 方法,让它们在完成数据操作后主动通知观察者。

import java.util.ArrayList;

class ObservableList<E> extends ObservableCollection<E> {
    private final ArrayList<E> delegate = new ArrayList<>();

    public boolean add(E e) {
        boolean ok = delegate.add(e);
        if (ok) {
            fireEvent(CollectionChangeEvent.Type.ADD, e, null);
        }
        return ok;
    }

    public E remove(int index) {
        E old = delegate.remove(index);
        fireEvent(CollectionChangeEvent.Type.REMOVE, old, null);
        return old;
    }

    public E set(int index, E e) {
        E old = delegate.set(index, e);
        fireEvent(CollectionChangeEvent.Type.SET, e, old);
        return old;
    }

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

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

在 set 方法中,我们既传了新值也传了旧值。这样观察者就能知道某个位置从什么变成了什么,比如做差值同步时非常有用。remove 只传旧值,因为元素已经不在集合里了。

这种实现方式对调用方完全透明:业务代码仍然像使用普通 List 一样调用 add 或 set,只是额外注册了一个监听器就能获得变动提醒,没有侵入原有逻辑。

2.2 注册监听器并使用

实际使用时,只需创建集合对象,添加监听器,然后执行变动操作,监听器就会打印或处理事件。

public class Demo {
    public static void main(String[] args) {
        ObservableList<String> list = new ObservableList<>();
        list.addListener(evt -> {
            switch (evt.getType()) {
                case ADD:
                    System.out.println("新增: " + evt.getElement());
                    break;
                case REMOVE:
                    System.out.println("删除: " + evt.getElement());
                    break;
                case SET:
                    System.out.println("修改: " + evt.getOldValue() + " -> " + evt.getElement());
                    break;
            }
        });

        list.add("苹果");
        list.add("香蕉");
        list.set(1, "橙子");
        list.remove(0);
    }
}

运行后会依次输出新增、修改、删除的提示。可以看到,监听逻辑与数据操作分离,如果以后要增加“变动写日志”的需求,只需再添加一个监听器,不必改集合本身的代码。

三、进阶考量与避坑

自定义可监听集合在简单场景下很好用,但生产环境还要注意几个问题,否则容易引发隐蔽 Bug。

3.1 防止通知期间的重入修改

如果某个监听器在收到 ADD 事件后又往同一个集合 add 元素,就会再次触发 fireEvent,形成递归调用甚至栈溢出。可以在 fireEvent 时加一个布尔标记,发现正在通知就暂存变更或抛异常,视业务而定。

abstract class SafeObservableCollection<E> extends ObservableCollection<E> {
    private boolean notifying = false;

    @Override
    protected void fireEvent(CollectionChangeEvent.Type type, E element, E oldValue) {
        if (notifying) {
            // 简单处理:忽略重入通知,或加入队列延后处理
            return;
        }
        notifying = true;
        try {
            super.fireEvent(type, element, oldValue);
        } finally {
            notifying = false;
        }
    }
}

上面用 notifying 标志保证同一时刻只发一轮通知。若业务必须支持重入,可把嵌套的事件放进队列,等本轮结束再补发,但复杂度会明显上升,需权衡。

3.2 弱引用监听器避免内存泄漏

如果监听器是匿名内部类且长期持有外部对象,而集合生命周期比外部对象长,就会造成内存泄漏。可用 WeakReference 包装监听器,或者提供明确的 removeListener 调用点。在 Android 或长期运行的服务中这一点尤为重要。

方案优点缺点
强引用+手动注销通知及时、逻辑简单易忘写注销导致泄漏
弱引用自动回收无需手动管理监听器可能意外被 GC

表格对比了两种常见做法。团队可根据集合归属关系选择:若是局部临时集合,强引用配合 try-finally 注销最直观;若是全局单例集合,弱引用更稳妥。

四、总结

通过观察者模式实现自定义集合,本质是把“数据变动”抽象成事件,并用接口隔离通知与处理逻辑。核心步骤包括定义事件与监听器接口、在主题中维护监听器列表、在集合写操作时触发通知,以及处理并发修改和内存泄漏等边界情况。掌握这套思路后,你不仅能写出可监听的 List,也可以轻松扩展到 Map、Set 甚至配置对象的属性级监听,让业务模块之间的协作更加清晰。

观察者模式自定义集合变量监听修改时间:2026-08-07 22:15:56

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。