知识卡片

丰富构造函数与空对象两种创建方式的权衡,及循环引用的解法

普通读书笔记卡

内容

数据映射器加载对象时,创建对象大体有两种方式。丰富构造函数:对象一创建出来就带着它必备的数据,作者更偏爱这种方式,因为对象从一开始就是完整有效的,还顺带解决了不变域的保护问题(构造完成后就不提供修改接口,天然保证这个值不会被后续篡改)。但这种方式有一个隐蔽的陷阱:如果两个对象彼此互相引用,加载其中一个时会去加载另一个,另一个又反过来触发第一个的加载,如此循环下去直到耗尽调用栈——要绕开这个坑,通常得写一堆专门处理特例的代码(比如结合延迟加载),而这类特例代码本身就容易写得凌乱,应当尽量避免。第二种方式:创建空对象——先用无参构造函数生成一个空白对象,立刻把它插入标识映射(在数据完全加载好之前就先占位),如果之后再遇到循环引用,标识映射会直接返回这个已经”提前登记”的空对象,从而终止无限递归的加载链条;代价是这类对象需要额外提供一些”仅限加载阶段使用”的设置方法,且这些方法一旦被滥用可能破坏对象的不变性保证,需要靠命名约定和状态检查加以约束。可迁移启发:处理”对象之间存在循环依赖、导致递归构造无限展开”这类问题时,”提前用一个占位符登记身份、后续再回填数据”是一个通用套路——先用最简单标识把这个实体在系统里”占个位”,即使内容还没填满,也能让后来的引用查到这个占位符从而打断循环,而不必绕着循环依赖专门写特判逻辑。

参考来源

- 位置:《企业应用架构模式》第二部分"模式"之"第10章 数据源架构模式"之"10.4.1 运行机制"(源文件:_epub-src/OEBPS/Text/000087.html) - 结论依据:原文说明"通过丰富构造函数创建的方法存在的问题是必须注意循环引用。如果两个对象彼此互相引用,则每次要加载其中一个对象时,该对象将试图加载另一个对象,而后者反过来又试图读入第一个对象,如此反复直到消耗完所有的堆栈空间……创建空对象可以做到这一点。用无参构造函数创建一个空白对象,然后将它直接插入标识映射中。这样如果存在循环,标识映射就会返回一个对象终止循环加载",直接支撑本卡结论。 - 原始内容:用无参构造函数创建一个空白对象,然后将它直接插入标识映射中。这样如果存在循环,标识映射就会返回一个对象终止循环加载。