只读迭代器是通过类型、接口和行为三层设计实现不可变访问的实践手段。它在ts/java/python中分别采用只读接口、不可修改集合和防御性拷贝,封装核心变量并禁用所有写操作,配合工程化检查与语义化命名确保安全性。
只读迭代器不是一种独立设计模式,而是将“不可变性”与“访问控制”嵌入迭代器协议的实践手段。它不靠运行时拦截赋值,而是在类型、接口、行为三个层面切断修改路径——让初级开发者即使想改,也找不到合法入口、编译通不过、ide会报错、测试直接失败。
用只读接口定义迭代能力,从源头禁用写操作
核心变量集合(如配置列表、权限规则集、状态枚举)对外不暴露可变迭代器,只提供明确标注 readonly 的遍历契约:
- TypeScript 中导出
IterableReadonly<t></t>或自定义ReadonlyIterator<t></t>接口,仅含next(): IteratorResult<t></t>和[Symbol.iterator](): ReadonlyIterator<t></t>,不包含任何set、remove、replace方法 - Java 中返回
Iterable<t></t>而非List<t></t>,或进一步包装为Collections.unmodifiableList(list)后再构造只读迭代器 - Python 中确保
__iter__()返回的对象只实现__next__(),且其__setitem__、append等方法被显式禁用或抛出TypeError
把核心变量封装进只读容器,迭代器只读取快照
迭代器本身不持有原始引用,每次 next() 返回的都是防御性拷贝或不可变视图:
- 若核心变量是对象(如
PaymentRule),迭代器返回的是Object.freeze({...rule})或结构体副本,而非原对象引用 - 若变量是数组或 Map,迭代器内部使用
Array.from()、new Map(map)或Immutable.List构造只读副本后再遍历 - 禁止在迭代器中暴露 setter、index 赋值、或任何可导致状态变更的链式调用(如
.push()、.add())
配合模块边界与构建检查,堵死绕过路径
光靠接口声明不够,需工程化加固,防止初级开发者通过 import 原始模块、强制类型断言、或反射方式越权:
- 核心模块不导出原始集合变量(如
export const rules = [...]),只导出getRulesIterator(): ReadonlyIterator<rule></rule> - CI 流程中启用 ESLint 规则(如
@typescript-eslint/no-explicit-any+ 自定义规则),禁止子模块中出现as any强转迭代器为可变类型 - Webpack/Rollup 配置
externals或module.noParse,阻止对核心模块源码的动态解析和 patch - 单元测试覆盖“尝试修改迭代项”的场景,例如:
const item = it.next().value; (item as any).status = 'hacked';—— 应确保该赋值无效且不影响原始数据
用语义化命名+文档约束,降低认知偏差
初级开发者常因命名模糊误判可操作性。只读迭代器必须在命名、注释、TS 类型名上形成强提示:
- 函数名明确带
ReadOnly或Safe前缀,如createReadOnlyConfigIterator(),而非getConfigIterator() - TypeScript 类型文件(
.d.ts)中为迭代器添加 JSDoc 注释:/** @readonly Iterates over immutable config snapshots. No mutation allowed. */ - 在团队开发规范中单列一条:“所有以
ReadonlyIterator结尾的类型,禁止用于赋值、索引修改、或作为可变集合传参”










