DDD聚合设计:计算器应接收聚合根还是实际操作的实体?
热力学计算库设计问题解答
核心结论
现有让JobackReidCalculator接收Flask的方案不合理,拆分出分子级、相级的计算器是更符合单一职责原则的设计,且可以通过合理架构避免复杂度上升和聚合根的问题。
为什么现有方案不合理
Joback-Reid这类方法的核心是基于分子结构(比如SMILES)计算分子属性,强行让它接收Flask会带来以下问题:
- 计算器需要额外编写从
Flask→Phase→Molecule的提取逻辑,承担了非核心的“数据提取”职责,违背单一职责原则 - 增加计算器与
Flask的耦合,后续若Flask结构变化,所有类似计算器都需要修改 - 无法直接复用该计算器计算单个分子的属性,限制了工具的灵活性
拆分计算器的可行方案
拆分出MolecularCalculator、PhaseCalculator、FlaskCalculator三类接口是合理的,可通过以下方式控制复杂度:
- 分层定义计算器接口:
每个层级的计算器只依赖对应层级的实体,以及下层计算的结果,职责边界清晰。# 示例伪代码 from abc import ABC, abstractmethod class MolecularCalculator(ABC): @abstractmethod def calculate(self, molecule: Molecule) -> dict[str, float]: pass class PhaseCalculator(ABC): @abstractmethod def calculate(self, phase: Phase, molecular_props: dict[Molecule, dict]) -> dict[str, float]: pass class FlaskCalculator(ABC): @abstractmethod def calculate(self, flask: Flask, phase_props: dict[Phase, dict]) -> dict[str, float]: pass - 适配MeasurementPlan的依赖解析:
依赖解析逻辑无需大改,只需按计算层级编排执行顺序:先执行所有分子级计算器,得到每个分子的属性;再用这些结果作为输入执行相级计算器;最后执行烧瓶级计算器。依赖关系依然可以基于“计算结果类型”定义,比如相级计算依赖分子级的沸点结果,MeasurementPlan只需确保分子级计算优先执行即可。
关于聚合根的担忧
拆分计算器不会影响Flask作为聚合根的地位:
- 聚合根的作用是控制领域模型的边界,确保内部实体(Phase、Molecule)的修改只能通过Flask进行,这属于领域模型的设计范畴。
- 计算器仅读取聚合根内部的实体数据进行计算,不需要修改这些实体,完全符合聚合根的设计原则。你可以在Flask中提供
get_phases()、get_molecules()这类方法暴露内部数据给计算器,既保持聚合根的边界控制,又满足计算器的数据需求。
额外优化建议
- 设计
CalculatorRegistry管理不同层级的计算器,方便MeasurementPlan查找和调用对应类型的计算器 - 对于需要跨层级的计算(比如从Flask直接触发分子级计算),可以在Flask中封装快捷方法,内部调用对应的
MolecularCalculator,对外保持简洁的API
内容的提问来源于stack exchange,提问作者Pablo Sánchez González
相关产品推荐
相关产品推荐

