知识卡片
值对象不可变的真正理由:避免别名bug
内容
引用对象和值对象的关键区别不在于大小,而在于判等方式:引用对象靠标识判等(内建对象标识或数据库主键之类的ID号),值对象靠内部属性值判等(两个日期对象只要年月日都相同就算相等,不管是不是同一个物理对象)。这个区别在实际处理上体现得很明显:值对象小、易创建,作为参数传递时通常是传值而非传引用,没人会关心系统里到底存在几个”2001年3月18日”这样的对象实例、也不关心两处引用是否指向同一个物理对象还是各自的等值拷贝。但很多语言并不原生支持”值语义”,这时让值对象正常工作的关键手段是让它不可变——一旦创建,属性就不再改变——而这么做的具体理由是避免”别名bug”:如果Martin和Cindy的受雇日期恰好都是3月18日、两人共享同一个日期对象实例,一旦Martin把自己的受雇日期改成5月,如果这个日期对象是可变的,Cindy的受雇日期会跟着莫名其妙地一起变——这显然不是任何人预期的行为。人们对”改一个小值”的直觉预期是”让Martin的日期引用指向一个全新的日期对象”,而不是”就地修改共享的那个对象”,值对象不可变恰好符合这种直觉。可迁移启发:当多处代码可能共享同一个对象实例、而这个对象又代表着一个概念上应该是”值”而非”身份”的东西时(日期、金额、坐标、区间……),让它保持不可变不是一种编码洁癖,而是从根源上避免”改一处、影响到看似无关的另一处”这类隐蔽bug的具体手段——任何语言只要缺乏原生的值语义支持,都该考虑用不可变性来补上这道防线。
参考来源
- 位置:《企业应用架构模式》第二部分"模式"之"第18章 基本模式"之"18.6.1 运行机制"(源文件:_epub-src/OEBPS/Text/000224.html)
- 结论依据:原文说明"使值对象能正常工作的方法之一是令其不变……这样做是为了避免别名bug。当两个对象引用共享同一值对象时,如果拥有者之一改变了对象的值,就会发生别名bug。例如,Martin的受雇日期为3月18日,而Cindy的受雇日期也是这一天。如果Martin后来将他的受雇日期中的月份改为5月,Cindy的受雇日期也会发生改变……通常对于这种小的值,人们的习惯思维是用一个令Martin的日期对象引用直接指向一个新的日期对象来改变受雇日期。使值对象不变符合这种预期",直接支撑本卡结论。
- 原始内容:不管这一改变是否正确,它并不是人们所预期的。