模型元素的模式

MODEL-DRIVEN DESIGN 中表示模型元素的模式有以下几种

  • ENTITY

  • VALUE OBJECT

  • SERVICE

对象的关联

模型中每个可遍历对象的关联,软件中都要有同样属性的机制。 例如,一对多的关联可以在一个实例中以集合的形式实现。 但是设计不一定如此直接,也可以使用一个访问方法(accessor method)来查询数据库,并通过记录来实例化对象。

设计必须指定一个特定的遍历机制,其行为与模型中的关联一致。

比如,现实中存在大量“多对多”关联,这些普遍的关联会使实现和维护变得复杂。 而以下方法可以使关联更易控制:

  • 规定一个遍历方向

  • 添加一个限定符,以便减少多重关联

  • 消除不必要关联

Entities(实体)

ENTITY,又称为 REFERENCE OBJECT

很多对象不是通过它们的属性定义的,而是通过连续性和标识定义的。 它们代表着贯穿时间的身份线,且常常经历多种不同的表示。 有时,一个对象必须与另一个对象相匹配,即使属性不同。例如,运输货物的载体可以是飞机,火车。 有时,一个对象必须与其他对象区分开来,即使它们可能具有相同的属性。例如,已完成订单上的货物和购物车上的货物。 错误的标识可能会破坏数据。

简单理解,主要由标识定义的对象称作实体。

实体有特殊的建模和设计思路。 具有生命周期,这期间它们的形式和内容可能发生改变。 为了有效跟踪,必须定义唯一标识。 实体的类定义、职责、属性和管理必须由标识决定。

实体建模

软件系统中的 “entities” 并不一定是通常意义上所指的实体。 只要对象满足两个条件:

  • 在生命周期中具有连续性;

  • 它的变化对用户不重要。

对 Entity 建模时,应该抓住对象定义的最基本特征,尤其时那些用于识别、查找或匹配对象的特征。 不要将注意力集中在属性或行为上。

实体的唯一标识可以由属性或属性的组合来表示。 当属性无法形成唯一键时通常可以附加一个 ID 属性作为标识,ID 一旦被指定就不可改变。 ID 通常由系统自动生成。

生成的算法必须确保 ID 在系统的唯一性,在分布式系统中,这是一个难题。

VALUE OBJECT

很多对象没有概念上的标识,它们描述来一个事物的某种特征。 用于描述领域某个方面而本身没有概念标识的对象称为 VALUE OBJECT。

一个对象是用来表示某种具有连续性和标识的事物,还是描述某种状态的属性。 是 ENTITY 和 VALUE OBJECT 的根本区别。

跟踪实体的标识非常重要,但为其它对象也加上标识会影响系统性能并增加分析工作,而且会使模型变得混乱。 然而,如果仅仅把这类对象当作没有标识的对象,那么就忽略了它们的工具价值或属于价值。 事实上,这些对象有其自己的特征,对模型也有这自己重要的意义。这些是用来描述事物的对象。

软件设计要时刻与复杂性做斗争。仅在真正需要的地方进行特殊处理。

当我们只关心一个模型元素的属性时,应把它们归类为 VALUE OBJECT。 我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。 VALUE OBJECT 应该是不可变的。所以不需要为其分配任何标识。

设计 VALUE OBJECT

在设计 VALUE OBJECT 是有多种选择,包括复制、共享或保持 VALUE OBJECT 不变。

复制和共享哪个更划算取决于实现环境。 复制可能导致系统被大量的对象阻塞(耗费高内存); 共享可能会减慢分布式系统的速度。

以下几种情况最好使用共享:

  • 节省数据库空间或减少对象数量是一个关键要求;

  • 通信开销很低时;

  • 共享的对象被严格限定为不可变时。

保持 VALUE OBJECT 不变可以极大地简化实现,并确保共享和引用传递的安全性,也符合值的意义。 如果属性的值发生变化,应该使用一个不同的 VALUE OBJECT,而不是修改现有的 VALUE OBJECT。

// 简单理解
let obj = { a: 1, b: 2 };

// better
obj = { ...obj, a: 2 };

// bad
obj.a = 2;

特殊情况下,仍需允许 VALUE OBJECT 是可变的:

  • 如果 VALUE 频繁改变;

  • 如果创建或删除对象的开销很大;

  • 如果替换将打乱集群;

  • 如果 VALUE 共享的不多或者共享不会提高集群性能,或其它某种技术原因。

如果 VALUE 的实现是可变的,就不能共享。

SERVICE

有时,对象不是一个具体事物。 设计时会包含一些特殊操作,这些操作从概念上讲不属于任何对象。 这时就顺其自然地引入了新的元素 —— SERVICE(服务)。

如果勉强把这些重要的领域归为 ENTITY 或 VALUE OBJECT 的职责, 那么不是歪曲了基于模型的对象定义,就是认为地增加了一些无意义的对象。

SERVICE 不应替代 ENTITY 和 VALUE OBJECT 的所有行为。

好的 SERVICE 有以下特征:

  • 与领域概念相关的操作不是 ENTITY 或 VALUE OBJECT 的一个自然组成部分;

  • 接口是根据领域模型的其它元素定义的;

  • 操作是无状态的。

SERVICE 不只存在领域层,也存在于基础设施层和应用层。 领域层和应用层的 SERVICE 与这些基础设施层 SERVICE 进行协作。

SERVICE 能够控制领域层中的接口粒度,避免客户端与 ENTITY 和 VALUE OBJECT 耦合。

Module

Module 也称为 Package,是一个传统的、较成熟的设计元素。

模块为人们提供了两种观察模型的方式从而解决“认知超载”。 一种是在模块内部查看细节,而不会被整体淹没。 二是观察模块与模块之间的关系,而不考虑内部细节。

模块之间应该是低耦合的,而模块内部应该是高内聚的。

所以,向领域设计中的其它元素一样,MODULE 是一种表达机制。 当你把一些东西放到 MODULE 中时,相当于告诉其它人员,这些类应该一起考虑。

MODULE 的名称应该是“通用语言(UBIQUITOUS LANGUAGE)”中的术语。 MODULE 及其它名称反映出领域的深层知识。

MODULE 需要与 model 的其它部分一同演变。 这意味 MODULE 的重构必须与 model 和代码一同进行。

分层架构(LAYERED ARCHITECTURE )可能导致模型对象实现分裂。 J2EE 早期时,把数据和数据访问放到 “实体 bean” 中,而把业务逻辑放到放到“会话 bean”中。 通过查看若干对象并脑补成单一地 ENTITY 是十分困难地,这种做法会破坏对象模型的内聚性。 加上“实体 bean”和“会话 bean”通常被隔离到不同的包中,使得情况更糟糕。

最好把事情变简单,极度简化技术分层规则。 这些规则要么对技术环境特别重要,要么有助于开发。

除非真正有必要将代码分不到不同的服务器上,否则就把实现单一概念对象的所有代码也放在同一个模块中。

利用打包把领域层从其它代码中分离出来。 否则,就尽可能让领域开发人员自有地决定领域对象地打包方式。

建模范式

模型驱动设计要求使用一种与建模范式协调的实现技术。 模型中地每个概念都应该在实现元素中反映出来。

领域模型不一定是对象模型。 模型范式只是为人们提供了思考领域的方式。

目前主流的范式是面向对象设计。 此外还有面向逻辑和事实构成的模型,

面向对象设计优势:

  • 大部分人比较容易理解面向对象设计的基本知识

  • 对象建模的丰富功能足以捕获重要的领域知识

  • 大量的工具支持(UML 等)

混合使用不同模型范式使得能够用最适当的风格对特殊概念进行建模。 但是,使用之前要确定主要范式的各种可能性都已尝试过。

最后更新于