状态隔离的本质:模块解耦、数据自治、副作用管控

在跨模块协作中,状态隔离是最底层、最关键的架构约束。它要求每个模块的 ViewModel 持有自己的状态副本,并通过明确定义的契约与其他模块通信,从而在编译期防止状态泄漏、在运行期控制副作用蔓延。本章从五个维度拆解这一本质,为后续章节的边界策略奠定基础。

1. 边界定义:模块解耦的物理与逻辑边界

解耦不是消除依赖,而是将依赖限制在接口层面。状态隔离要求模块之间只通过 纯数据通道(如通道、回调、总线)交换信息,绝不共享 ViewModel 内部状态引用。物理边界体现在独立编译单元,逻辑边界体现在状态所有者唯一。

边界类型描述示例
物理边界模块以独立库或服务形式存在,不内聚其他模块的状态类A模块无法import B模块的ViewModel
逻辑边界数据所有权清晰,禁止跨模块直接修改状态模块B只能通过A暴露的接口读取状态快照
运行时边界状态容器生命周期与模块绑定,模块销毁时状态自动释放ViewModel的onCleared不会泄漏到外部
关键原则:任何状态的写操作必须发生在所属模块的内部;读操作必须通过无副作用的查询接口,且返回不可变快照。

2. 分层策略:职责与信任关系

跨模块 ViewModel 边界通常分为三层:

  • 接口层(边界契约):定义模块对外提供的状态查询命令、事件订阅方法,不暴露内部实现。
  • 协调层(跨模块调度):处理跨模块事件路由、状态聚合,但不持有其他模块的状态副本。
  • 自洽层(本地状态管理):模块内部的 ViewModel + UseCase + Repository 组合,完全控制自己的状态与副作用。

这三层形成严格的 信任链路:上层只能通过下层公开的接口获取数据,下层永远信任上层传递的命令,但不确定上层如何使用结果。

3. 组件职责:各司其职,不越界

组件职责隔离规则
ViewModel管理界面状态、收集用户事件、调度业务逻辑不持有其他模块的ViewModel引用
UseCase / Interactor编排数据流,调用多个Repository不直接访问UI层;返回结果不可变
Repository提供数据源统一访问,封装本地/远程存储不暴露内部缓存的引用,仅提供Snapshot
跨模块桥接器转换模块A的事件为模块B的接口调用无状态,纯函数式映射
实践检查:如果模块A需要修改模块B的ViewModel中的字段,说明边界已被破坏。正确的做法是模块B暴露一个“命令接口”,由模块A调用,然后模块B自行更新状态。

4. 数据流 / 控制流 / 信任链路

数据流从产生到消费严格遵循“向下传递、向上反馈”的链式结构:

  • 数据流方向:用户事件 → 所属ViewModel → 内部UseCase → Repository → 数据源。跨模块数据交互通过桥接器以事件或异步流形式流动。
  • 控制流方向:命令从外部模块 → 桥接器 → 目标模块接口层 → 内部处理,控制权始终在接收模块。
  • 信任链路:模块A信任模块B提供的接口签名,但不信任其内部实现细节;模块B信任模块A传递的命令参数,但不对命令来源进行身份校验(仅在架构层面信任)。
常见风险:跨模块直接读写状态会导致“隐式依赖”——模块A修改B的状态后,B的行为依赖A的时序,一旦A改动,B可能出现非预期状态。必须通过事件和不可变数据来切断这种隐式依赖。

5. 选型权衡

不同隔离策略各有利弊,下表对比三种主流方案:

方案优点缺点适用场景
接口隔离(依赖倒置)编译期强约束,易测试接口膨胀,维护成本高大型项目,需要严格契约
事件总线 / 消息中间件弱耦合,新增监听方便调试困难,时序不可控跨模块通知类场景(如登出、主题切换)
共享状态工厂集中管理跨模块共享数据违背自治原则,引入单点风险极小规模模块组,且只有只读共享

6. 风险点

  • 循环依赖:模块A需要模块B的状态,模块B又需要模块A的状态,形成逻辑死锁。规避方法:始终保持依赖方向为“上层→下层”单向。
  • 状态不一致:多个模块缓存同一实体,写入时序不一致导致脏数据。需要引入乐观锁或版本号机制。
  • 副作用逃逸:ViewModel在onCleared后仍有匿名回调执行,修改已销毁模块的UI。必须清理所有跨模块订阅。
  • 性能损耗:过度抽象导致每次跨模块调用都序列化/反序列化状态。需在热路径上提供批量查询或分页。

7. 落地步骤

  1. 定义模块边界图:画出现有模块的依赖关系,识别循环依赖和跨模块直接引用。
  2. 对每个模块提取接口层:创建纯数据契约(接口/协议),只暴露必要查询和命令。
  3. 引入跨模块桥接器:将原有跨模块直接调用替换为桥接器调用,桥接器内部完成类型转换与参数校验。
  4. 迁移状态到内部:检查每个ViewModel中是否有其他模块的状态引用,将其改为通过接口获取快照。
  5. 增加副作用管控:在桥接器出口和入口增加日志/监控,记录每次跨模块调用,便于调试。
  6. 编写验收测试:模拟模块销毁、重建场景,验证状态不泄漏。

8. 验收标准

验收项通过条件验证方式
无跨模块状态引用代码扫描显示模块A未直接引用模块B内部类依赖分析工具
数据一致性同时修改同一实体时,最终状态与预期一致并发测试用例
副作用受控模块卸载后无残留任务或回调泄漏检测(LeakCanary等)
信任链路明确所有跨模块调用都经过桥接器或接口层架构规则检查(ArchUnit)
状态隔离本质:三模块协作模型 模块A ViewModel A 状态: {user, } 副作用: 网络请求 模块B ViewModel B 状态: {orders, cart} 副作用: 本地数据库 模块C ViewModel C 状态: {notifications} 副作用: WebSocket 跨模块桥接层(接口契约 + 事件总线) 只传递不可变快照、纯命令;不持有任何模块的状态引用 模块解耦 编译期独立,运行时隔离 数据自治 状态所有权唯一,外部只读 副作用管控 生命周期跟随模块 图1:跨模块状态隔离核心架构——各模块持有自身状态,通过桥接层交换不可变数据
本章核心:状态隔离的本质不是消除所有跨模块交互,而是将交互严格限定在“无状态、无副作用”的桥接契约中,让每个模块成为自身状态的主人。模块解耦、数据自治、副作用管控三者相辅相成,是判定架构是否健康的三大黄金指标。