DDD话语评价之二:“值对象”是DDD的创新吗(全文)

节选自《软件方法(下)》。

8.2.8 评价DDD话语中的“值对象”

在识别类的时候,有的建模人员受到DDD话语体系的影响,会着急去分辨哪个类是实体(Entity),哪个类是值对象(Value Object),这是没有必要的,而且很容易成为遮掩无能的遮羞布。

8.2.8.1 历史回顾:不可变对象

1986年,Barbara Liskov和JohnGuttag在其讲述面向对象思想和CLU编程语言的书“Abstractionand Specification in Program Development”中,提到有两种对象:可变的(mutable)和不可变的(immutable),如图8-62。

图8-62 摘自_Abstraction and Specification in Program Development_, Liskov B. & Guttag. J. , 1986

******

CLU是Barbara Liskov和她的学生在1974-1975年间创造的编程语言,对后来的面向对象编程语言有重要影响。2008年,Barbara Liskov因在数据抽象、分布式计算和容错方面的贡献获得图灵奖。

Abstraction and Specification inProgram Development无中译本。2000年,这两位作者又出了一本Program Development in Java: Abstraction, Specification, andObject-Oriented Design,所用的编程语言改成了Java。这本书有中译本,如图8-63。

******

图8-63 摘自《程序开发原理:抽象、规格与面向对象设计》,Liskov B. & Guttag. J. 著,裘健译,英文原版出版于2000年

再列早期一些使用“不可变对象”的文献,如图8-64和8-65。

图8-64 摘自_Seamless Object-Oriented Software Architecture_, Walden K. , & Nerson J. , 1994(本书无中译本)

******

Seamless Object-Oriented SoftwareArchitecture基于Bertrand Meryer的思想,作者是ISE Eiffel 2.2开发环境的主要开发者和BON(Business ObjectNotation)的发明者。ISE Eiffel是InteractiveSoftware Engineering(由Bertrand Meryer创建)开发的Eiffel语言IDE,最初发布于1986年,现已改名为EiffelStudio,最新版本20.11。BON是类似于UML的建模表示法。

******

图8-65 摘自Non-Interference Properties of a Concurrent Object-Based Language:Proofs Based on an Operational Semantics, Hodges S. & Jones C. , 1995

现在,“不可变对象”依然在广泛使用,如图8-66。除了面向对象的书籍之外,更多的是出现在讲述函数范式的书籍中。

图8-66 摘自_Seriously Good Software: Code that Works, Survives, and Wins_, Faella M. , 2020

******

Seriously Good Software的中译本起名《你真的会写代码吗》,已于2021年7月出版。此处非广告。我未和出版社联系过,也不欣赏中译本乱改名的行为。提到此书只是随手举例,不代表推荐或不推荐阅读。

******

8.2.8.2 历史回顾:值对象

Martin Fowler和Kendall Scott在“UMLDistilled”的第一版使用了“值对象(Value Object)”一词,如图8-67。

图8-67 摘自_UML Distilled: Applying the Standard Object Modeling Language_, Fowler, M. & Scott, K. , 1997(此版本无中译本)

Martin Fowler在他后续出版的书中继续使用“值对象”,如图8-68和图8-69。

图8-68 摘自《重构:改善既有代码的设计》,Martin Fowler 著,侯捷、熊节 译,英文原版出版于1999年

图8-69 摘自《企业应用架构模式》,Martin Fowler 著,王怀民、周斌 译,英文原版出版于2003年

J2EE话语体系也曾使用“值对象”,但有另外一种含义,相当于数据传输对象(Data TransferObject),如图8-70。Martin Fowler在《企业应用架构模式》中讲述“值对象”模式时,提到了这一点。

图8-70 摘自《J2EE核心模式》,Alur D. 等 著,牛志奇 译,英文原版出版于2001年

******

《J2EE核心模式》(第2版)已不用“值对象”,改用“传输对象(Transfer Object)”。

******

“值对象”目前主要用在DDD话语体系中。您可以观察近年出版的书籍,里面提到“值对象”的地方,很可能在这个词的周围还会提到“实体”“领域驱动设计”“DDD”等。

也许有人会说“值对象”和“不可变对象”不是一回事。你看,名字都不一样嘛,说明侧重点不同。“不可变对象”可以有标识,Eric Evans甚至还说“值对象”可以改变属性值。

其实,相对于“值对象”的命名,“不可变对象”的命名更本质。我们更在意的是属性值是否可变,而不是有没有标识、如何判断相等。在8.2.8.4会进一步讲述。

8.2.8.3 回顾历史,警惕伪创新

翻出历史来,意思是说**“值对象”的概念不是Eric Evans发明的,也不是Eric Evans给起的名字。**

这一点并非所有人都了解,如图8-71中的表述。

图8-71 摘自《解构领域驱动设计》,张逸著,2021

******

顺便再吐槽一下,图8-71中“面向对象设计的基本原则,如信息专家模式”的表达是不严谨的,原则和模式不是一个级别的东西。

以面向对象来说,被归纳的“原则”的数量最多也就两位数,最出名的是所谓的SOLID,而“模式”的数量就多了去了。

GoF(1995)有23个模式;Kent Beck的SmalltalkBest Practice Patterns(1997)有92个模式(就是格式不太规范);POSA(面向模式的软件架构)系列从1996年到2007年出了5本,作者说有114个模式;PLoPD(程序设计模式语言)系列从1995年到2006年也出了5本,其中收录的模式数目查不到,我也没得数,但PLoPD每一本的页数是对应POSA的近两倍;Fowler的《企业应用架构模式》有51个模式(“值对象”就是其中一个)……现在每年依然有新的模式书出版,去除那些变着花样复刻GoF赚流量的垃圾书后,还是有一些书贡献了新模式。

还有,PLoP年年开会,今年是第28届了。

******

如果不了解历史,就有可能会被某些伪创新的宣传所蒙骗。永动机、水变油的伪创新过一段时间就会改头换面出来收智商税,原因就是我们对历史教训的记忆太容易消失了。

如果人们得知一个东西曾经存在过,那么当这个东西再次被拿出来宣传时,人们会对宣传保持较多的理性,“这东西如果真的这么厉害,那之前怎么……”,宣传的人也会收敛,不至于那么夸张。

伪创新会选择换个名字,称自己是“全新的”、“革命性的”,给人一种从未有过的、从天而降的感觉。因为是“全新的”,所以再怎么夸大宣传,人们也还是会给一个机会,毕竟是“新”的,没准人家真的有这么牛呢。

例如,说青霉素可以治愈肝癌,大众肯定不信,要是真的那么多年不早就验证了嘛;如果把青霉素改个名字叫“K9527-α”,说可以治愈肝癌,可能就会有人买了试试。

正如前文(8.2.6.2)所说,伪创新还会有意割裂和已有知识的联系——我是“新”的,不受已有知识的约束。这样,在受到他人批评时,就可以巧妙辩解“你说的鹿和我说的鹿不一样”。

伪创新的宣传中往往会带有“艺术”、“禅”、“道”等字眼,有意无意地朝宗教、艺术、玄学方向引导——这些东西信仰是主要的,道理是次要的。

以上内容并非说“值对象”是伪创新,而是说要警惕过分的宣传——同样适用于UML及其他。

8.2.8.4 本书关于“值对象”的观点

对象本就应该是可变的

“面向对象”就是把某些数据和经常操纵这些数据的行为封装在一起变成类,以此作为系统的基本构造块,如图8-72。

图8-72 类把行为和数据封装在一起

如果说把经常在一起出现的语句集封装成子程序是一级封装,那么把经常在一起出现的数据和行为封装成类可以看作二级封装。

并非随随便便封装就能带来好处。像下面这些经常看到的有规律一一对应的“面向对象”封装:

*每个属性刚好对应一对getter/setter操作(现在的Property语法就更省事了),然后就说封装了,面向对象了。

*把某个或某几个行为凑成一个类,类的名称叫"***er"或“***or”,行为变成类的操作,然后就说封装了,面向对象了,再加上一个泛化结构,更是感觉高大上。Robert C. Martin写的书里面很多地方就是这种er、or类,没有属性,全是操作,然后SRP、OCP什么的喊一通,很多人以为这样就“面向对象”了。

这样的“面向对象”也不是一点用都没有,但还远远不够。

软件的复杂性在于,行为和数据不是一一对应的。某个属性值可能会被多个行为使用和改变,某个行为可能会使用和改变多个属性值。应该用哪些属性值来计算,怎么计算,会修改哪些属性值,怎么修改,这些行为规则封装在类中,可以通过状态机描述。状态表征了对象表现出相同行为规则的属性值组合,把行为和数据连接起来。

如果没有做到这样的封装,或者认为没有必要做到这样的封装,那面向对象的意义就不大了。如果一味强求属性值或状态“不可变”,那完全可以采用另外一种思考范式嘛。

不过,天上不会掉馅饼,即使换了另外一种思考范式,依然要面对上面提到的行为规则复杂性,只不过是换了一种方式表达而已。

关于结构共享(别名错误)

说到对象“不可变”的好处时,往往会提到一个Grady Booch称为“结构共享(Structural Sharing)”的问题,如图8-73和8-74。

8-73摘自Software Components WithAda: Structures, Tools, and Subsystems, Booch G. , 1987(此书无中译本)

8-74 摘自《面向对象分析与设计(原书第2版)》,Grady Booch 著,冯博琴 等 译,英文原版出版于1994年

Martin Fowler把它称为“别名错误(Aliasing Bug)”,如图8-75。

图8-75 摘自https://www.martinfowler.com/bliki/AliasingBug.html

******

常见的应对方法是把setDate()改成返回一个Date,partyDate.setDate(5)变为:

partyDate=partyDate.setDate(5);

例如,C#中的DateTime值类型就是这样处理。

但是,如果从领域逻辑上认为“A就是B”,那么出现图8-75的结果就是应该的,所以问题的根源在于“A就是B”是不是对的,而不在于A、B能不能变化。

以图8-75为例,说派对日期就是退休日期是不合适的,本来就不应该是同一个实例,只能说两者的日期相同(业务规则可能会变化为“派对日期安排在退休日期7天后”)。不管是用另外的操作来取代“=”还是重载“=”运算符,应该体现这样的逻辑。

图8-75例子中,partyDate和retirementDate的类型都是Date,不是文中暗指的某应用系统的核心域概念。更合适的抽象可能如图8-76,哪一个最合适看具体情况了。

图8-76 更合适的抽象

另,Fowler关于Aliasing Bug最早用的并不是图8-75的例子,而是如图8-77。估计作者后来也是觉得不太合适:什么样的领域逻辑会让人写出导致Martin的受雇日期和Cindy的受雇日期引用同一日期对象的代码?从这一点小细节也可以看出,还是要从领域逻辑的角度来解读。

图8-77 摘自《企业应用架构模式》,Martin Fowler 著,王怀民、周斌 译,英文原版出版于2003年

不用急于去划分“实体”和“值对象”

按照本书前文所说的内容,识别和精化类和属性,再按照本书后文所说的内容建模类的状态和行为,你会发现,只需实事求是描述领域内涵,结果会自然而然显露出来,并不需要套上“实体”和“值对象”的概念。

即使为了附和DDD的“新话”一定要套上“实体”和“值对象”的概念,也不要急匆匆去套上。实际上,你也不能。没有对一个类作充分的建模就武断地针对这个类做出判断,证据是不充分的,只能算胡说八道。

和上册的推导需求一样,没有经过业务建模的思考,张嘴就说“本系统有**功能”,只能算胡说八道。

胡说八道没问题,甚至向其他人宣传自己的这些胡说八道是严密的推导也可以理解,但至少不要自己骗自己,真心以为这就是最佳的做法。

我们看Eric Evans的《领域驱动设计》中是怎么说值对象的,如图8-78。

图8-78 摘自《领域驱动设计:软件核心复杂性应对之道》,Eric Evans 著,陈大峰 等 译,英文原版出版于2003年

你看,“性能”、“性能”。**Eric Evans在这个地方以性能为理由来强调“值对象”的重要,其实是不合适的。**如果从领域逻辑出发,推导出需要“电话号码”类、“日期范围”类,这可以,以性能为由不合适。

面向对象(其他建模范式也一样)的思考首先是为了让有限的人脑资源能有办法去应对复杂的逻辑,而不是为了性能。我们在前文已经说了,在分析工作流中一旦让“性能”这个东西混进来,很容易导致废话刷工作量,a+b+c变成a×b×c——当然,前文也说过多次,这种不用思考就可以刷很多工作量的结果,可能正是某些开发人员乐意看到的。

关于“值对象”的命名

在DDD话语体系中,“值对象”和“实体”并列,这个命名是不太严谨的。

“值”后面有个“对象”,那“实体”后面怎么不加个“对象”呢?要么都加,“值对象”和“实体对象”,要么都不加,“值”和“实体”。

实际上,“值对象”和“实体”讨论的是类,说“这是一个值对象类”是比较奇怪的,如果一定要加后缀,改成“值类”或“值类型”和“实体类”更合适。

Martin Fowler在博客中(https://www.martinfowler.com/bliki/ValueObject.html)还提到“值对象”和“实体”的命名不符合二分法的问题,更推荐用“引用对象”和“值对象”。另一种划分就是前文所说的“不可变对象”和“可变对象”了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值