调整类可见性与使用final等约束不能阻止双向组合本身,但能通过包/模块隔离、禁止继承、只读接口暴露及静态分析拦截来切断隐式依赖、防止循环持有、约束协作边界。

调整类可见性作用域和使用final(或等效约束)不能直接阻止“双向组合”本身,但能有效限制其滥用场景——关键在于切断隐式依赖、防止循环持有、约束协作边界。
用包级/模块级可见性隔离组合关系
将参与组合的类限定在同一个包(Java)、内部程序集(C#)或模块(Python + __all__ + 目录级导入控制)内,可天然阻止跨业务域的随意组合。
- Java:把
OrderProcessor和它依赖的InventoryService放在同一 package,构造器设为 package-private;外部包无法 new 出实例,也就无法在不知情时反向持有OrderProcessor - Python:在
order/模块中定义class InventoryAdapter:,并在__init__.py中不导出该类;其他模块 import 时无法直接引用,只能通过order.get_inventory_client()这类受控工厂函数获取
禁止类被继承 + 显式声明组合契约
把组合主体类设为 final(Java/C++)、sealed(C#)或 Python 中用 __slots__ = [] + 私有构造器 + 类方法工厂,从语言层关闭子类篡改行为的可能。
- 避免写
class OrderService extends BaseService,再在里面持有一个NotificationService;而应写final class OrderService,且只接受NotificationClient接口(而非具体实现类)作为构造参数 - 这样既封住继承链,又迫使组合对象必须满足契约,而不是任意类型都能塞进来形成隐式双向引用
用只读接口暴露依赖,禁止反向赋值
组合方持有的依赖对象,不暴露可变引用,只提供只读方法或事件回调入口。
- 例如
OrderService持有readonly IUserContext,里面只有getUserId()、hasPermission(),没有setToken()或updateProfile() - 若需通知上游状态变化,统一走
onStatusChanged回调或发布领域事件,而非让下游直接调用上游的 setter 方法
静态分析+CI拦截非法组合模式
仅靠编码约定不可靠,需工具链强制:
- ESLint / SonarQube 规则:禁止
import跨模块的 service 类(如from '@/modules/user'在 order 模块里) - Java Checkstyle:检测构造器参数是否为具体实现类(如
new OrderService(new EmailNotifier())),要求必须是接口 - CI 流程中运行依赖图扫描,标记出
order → user → order这类环形引用并阻断合并

















