《领域驱动设计》读书笔记(3)—— 软件中的模型描述

第5章 软件中的模型描述

用面向对象来为领域建模,又可以为把这些对象分成若干类,进而抽象出了领域驱动设计中的模型要素。

5.1 关联

对象之间的双向关联虽然会让某些操作变得简便,便是会使代码变得难以理解和维护,因此应该简化对象之间的关联关系:
  • 指定一个导航方向
  • 通过加入限定符来有效地减少关联的多重性
  • 清除不必要的关联
坚持对关联进行严格的约束,如实反映领域中的使用偏向,不仅仅会让这些关联更易于交流、简化其实现难度,还能突出剩下的双向关联的重要性。当一个双向关系是领域中的一个语义特性时,当它是应用系统的功能必须实现的需求时,我们就要保留双向的导航来体现它。 显而易见,最极端的简化方式是:如果关联不是手头任务的本质,或者不能反映出模型对象的基本含义,那么它就应该被完全取消。

5.2 实体(又称引用对象)

有些对象并不主要是由它们的属性来定义的。它们体现了标识在时间上的延续性,经常要经历不同的形态。有时,一个对象与另一个对象有着不同的属性,但它们是相互匹配的;有时,一个对象与另一个对象有着相同的属性,但是它必须能象跟那些对象区分开来。弄错对象标识会导致数据破坏。 以标识作为其基本定义的对象称为实体。 如果一个对象是通过标识而不是属性来确定的,那么就在模型中把标识作为这个对象定义的基本要素。保持类的定义简单明了,并着重考虑其生命周期的连续性和惟一性。定义对象的方法要能够把每个不同的对象区分开来,而无需去考虑它的形态和历史。对那些需要匹配通过属性比较来匹配对象的情况保持警惕。确保生成标识的操作能够为每个对象生成一个唯一的结果,这可以通过在标识中附加一个具有惟一性保证的符号来实现。生成的标识可能来自外界,也可能是由使用这个标识的系统随机产生的;不管是用什么方法,标识必须满足其在模型中所具有的惟一性。模型必须对“怎么才是同一个事物”的具体含义作出定义。 实体(entity)这个词在Java EE开发中被滥用了,更多地表示的是映射到了数据库表的类,一定要把这两个概念区分开。 同样一个事物,在不同的问题域里面,不同的领域里面,可能是实体,也可能不是实体,一切要看在它的领域里面,人员最关心的是它的属性,还是它的标识。

5.3 值对象

如果一个对象代表了领域的某种描述性特征,并且没有概念性的标识,我们就称之为值对象。值对象就是那些在设计中我们只关心它们是什么,而不关心它们是谁的对象。 如果我们只关心模型中一个元素的属性,那么就把这个对象划分成值对象。用它来描述它所要表达的那些属性的意义,并提供相应的功能。把值对象看成是不可变的。不要给它任何标识,这样可以避免实体的维护工作,降低设计的复杂性。 值对象会作为值赋给不同的实体,为了防止由于共享对象而引起的副作用,最好是把它实现成不可变对象。但是,实现不可变对象需要额外的开发工作量,在有些情况下可能还会导致对象过多的问题,所以得根据具体情况具体分析。总之,并不总是要把值对象实现成不可变对象,是不是不可变对象也不是区分值对象的标准。

5.4 服务

领域中的一些概念不能作为模型中的对象来处理,将领域需要的功能强制加给实体和值对象,不仅会破坏模型中对象的定义,而且还会人为的添加不必要的对象。 服务是领域模型中一类特殊的对象,它不代表领域中一个具本的东西,而是代表了一个领域中独立于实体与值对象之外的一些纯粹的功能。好的服务具有以下三个对点:
  1. 与领域概念相关的操作不是实体和值对象中固有的部分
  2. 接口根据领域模型中的其它模型来定义
  3. 操作是无状态的
服务也是一个容易被滥用的概念,在贫血模型这咱错误的设计里面,就把所有的功能都放在服务中完成,而让实体和值对象不再拥有领域功能,变成了只包含数据的贫血变种。服务应该具有确实的领域上意义,它可以代表一个外部系统给领域提供的一个功能,也可以代表了领域中一个在多个实体和值对象间协作完成的操作,而这个操作不属于任何一个实体或值对象。 当领域中的一些重要的进程或转换操作不是实体和值对象本身的职责时,把操作作为一种独立的接口加入模型,并声明为服务。根据模型中使用的语言来定义接口,保证操作名是通用语言的一部分。使这个服务变成无状态。
5.4.1 服务和分隔的领域层
不仅要把服务与实体和值对象区分开来,还要把领域模型里的服务与其它层次里的服务区分开来。领域模型里的服务完成了一个领域里的业务操作,它的名字来源于领域的通用语言,它的输入输出参数也是领域模型;而其它层次的服务则完成了其它层次内的一个具体的功能。比如,通知客户订购成功,通知哪个客户的哪个订购,通知他哪些事实,这是领域上的服务要完成的,而用邮件还是短信来通知,如何发送邮件或者短信等则是基础结构层要完成的。
5.4.2 粒度
由于实体和值对象所实现在领域功能往往粒度比较细,如果直接让应用层调用这些实体和值对象,会过多暴露领域层的内部实现,会让应用层使用不方便,那么在领域层用一个服务来把这些操作包装成一个粒度较大的操作也是合理的。这时候应该给这个服务层取个有领域意义的名字,并加入到通用语言当中。

5.5 模型(包)

每个人都使用模块,但是很少有人将它们看作是模型中一个完整的部分。在开发人员使用的技术框架中,代码被分门别类地进行划分。甚至那些地经常重构模块的开发人员,也常常使用以前项目中设计好的模块。 的确应该实现模块间低关联与模块内的高内聚。对于关连和内聚的解释,听上去往往像是技术上的衡量标准,用来机械地判断模块相互联系和相互作用的分布情况。除代码外还将概念都分成了模块。一个人在某个时刻所能考虑到事情是有限的(低关联)。不连贯的思维是很难被理解的,也很难表现出统一的想法(高内聚)。 选择的模块应该能够描述系统,包含的概念集应该是内聚的。这样通常会在模块间产生松散关联,如果不是,就想办法改变模型,重新整理概念。或是寻找一种概念,构成模块的基础,以一种有意义的方式把元素集中起来。寻找概念中的低关联,能够理解并解释它们之间的相互独立性。对模型进行精练,直到能够根据高级的领域概念进行划分,同时分离相应的代码。 给模块命名,并把这些模块名加到通用语言中。模块和它们的名称应该反映出对领域的理解。 模块也是领域驱动设计中模型的一个元素。随着模型变得复杂,模型内的对象会变多,变得复杂,要想办法把这些对象划分到不现的模块中,否则到了一定的规模后,模型会复杂到难以掌握。
5.5.1 敏捷的模块
5.5.2 基础结构驱动打包的缺陷
某些基础结构的框架,往往会对打包有要求,如果模块的划分打包的方式由这些框架来驱动,那么很可能导致本来属于同一个模块的领域对象的代码被分散到了不同的包中,让模型代码变得难以理解。 除非真的是想将代码分布到不同的机器中,否则将实现一个概念对象的所有做代码都包括在同一个模块中,即使这些代码实现的不是同一个对象。 使用打包技术从其他代码中分离领域层。否则,尽可能地让领域开发人员根据他们的模型和设计来打包领域对象。

5.6 建模范式

除了面向对象之外,还有其它的建模范式,比如面向规则,面向流程,等等。面向对象是实现领域模型设计的一个成熟可靠的范式,但对于某些特殊的领域,也不排斥人们使用其它的范式来对领域建模。如果在同一个项目中需要使用多个建模范式,也会使项目变得复杂,领域模型不容易掌握。因此,对于大部分的情况,还是应该多挖掘面向对象的能力,使用面向对象来描述领域。

转载于:https://my.oschina.net/komodo/blog/919181

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值