Encapsulate Collection 是什么
返回集合时给出只读视图或副本,修改走专用方法。这样调用方无法绕过校验与通知就地增删,集合的所有变更点收敛到类内部;把「谁能改、改的时候要做什么」明确下来,集合就不再是隐形的公共可变状态。
基本信息
| 规则 | 读给只读视图或副本,写走显式方法 |
|---|---|
| 场景 | 实体暴露内部列表、调用方拿到后就地增删 |
| 配套 | 把入参与出参一并封装,校验集中在专用方法 |
| 权衡 | 要不要防御性拷贝取决于集合大小与调用频率 |
要点与选择
- getter 返回不可修改视图,写操作通过 add/remove 方法暴露
- 需要绝对安全时返回副本,代价是每次调用的复制开销
- 新建集合的方法比传入集合更安全:由类自己决定如何合并
- 专用方法里可以顺带做去重、排序、计数与事件通知
- 元素本身可变时,只读视图挡不住元素被改,必要时再封装元素
- 暴露集合规模与是否包含等查询方法,避免调用方为了看一眼长度而取全量
Java 示例
Java 示例
public class Course {
private final List<Student> students = new ArrayList<>();
// 读:返回不可修改的副本,外部改不动内部结构
public List<Student> students() {
return List.copyOf(students);
}
public int size() { return students.size(); }
// 写:校验与通知都收在这里
public void addStudent(Student s) {
if (s == null) throw new IllegalArgumentException();
if (students.contains(s)) return;
students.add(s);
events.publish(new StudentEnrolled(s));
}
}各语言常见做法
| 语言 | 只读视图 | 副本 |
|---|---|---|
| Java | Collections.unmodifiableList / List.copyOf | new ArrayList<>(list) |
| C# | IReadOnlyList<T> | list.ToList() |
| Python | tuple(seq) / MappingProxyType | list(seq) / dict(d) |
| Go | 无内置视图,靠不导出字段 | append([]T(nil), s...) |
不可修改视图与副本语义不同:unmodifiableList 只是视图,底层集合若被内部代码修改,视图内容也会变;List.copyOf 是快照,改动内部集合不会影响已返回的副本。选择取决于调用方是否需要看到最新状态。
只读集合挡住的是「结构被改」,挡不住「元素被改」:如果元素本身可变,调用方仍能修改元素内部字段。需要真正不可变时,还得让元素类型不可变,或者返回只读的投影对象。