DDD领域驱动设计批评文集
做强化自测题获得“软件方法建模师”称号
《软件方法》各章合集
8.2 建模步骤C-1 识别类和属性
8.2.2 三种分析类
8.2.2.2 关于边界类
边界类的责任是接受输入、提供输出以及做简单的过滤。
图8-20中提到边界类的映射方法——每个有接口的外系统映射一个边界类。这里说的"有接口的外系统"不仅包括系统执行者,还包括仅接受系统输出信息的外系统。
以2018版《软件方法(上)》UMLChina系统案例中的"时间→发送公开课通知"用例为例。该用例进行过程中,系统会向软件开发人员发送公开课通知,同时还要向UMLChina助理反馈发送通知的进展。软件开发人员和UMLChina助理在这个用例中仅仅是接受输出,没有输入信息给系统,但系统可以分别设置一个边界类来封装向软件开发人员和UMLChina助理反馈信息的责任,如图8-22所示。
图8-22 外系统映射边界类
图8-22中,“时间”映射一个“时间接口”,“软件开发人员”映射一个“软件开发人员接口”,“助理”映射一个“助理接口”。
外系统如果是人,对应的边界类也可以叫“**界面”,例如“助理界面”。本书就不区分了,一律起名“**接口”。
分析工作流的边界类不暗示任何实现方案。在总责任相等的前提下,它和实现的映射是多样的,可以是图形、文本、语音、远程调用……。
即使使用图形界面实现,也不能简单认为一个边界类对应一个窗体(Form)。一个边界类的责任可以拆解到多个窗体上,一个窗体也可以和多个外系统交互。如何组织这些责任,应该从外系统的角度来考虑,而不是从用例或实体类的角度来考虑。
图8-23中,“助理接口”边界类被圈住的几个责任来自不同用例的步骤,但在使用图形界面实现时,可以放在面向助理的、通知专用的同一个窗体中。
图8-23 边界类责任的组织
类似的例子还有:一份申请,需要通过系统审批三次,也就是三个不同的用例。在图形界面实现中,可能不需要准备三个窗体,部门主管、财务、副总三个审批人可以在同一窗体上工作,但部门主管、财务、副总各自有对应的分析边界类。
如果某个外系统和系统的交互很多,对应边界类的责任可能会有很多。有的做法推荐按"外系统+用例"的组合映射边界类,这样可以减少一个边界类上的操作个数。本书不推荐这样做,因为这已经隐含着先入为主“按用例划分边界”的意思,不利于最后得到合理的边界。
尽量保持一个外系统映射一个分析边界类,如果操作很多,可以将从外系统角度观察可能要分在一组的操作移到一起,EA等工具可以随意定制属性和操作的上下显示顺序。
需要提醒的是,外系统映射的只是边界类,并不映射实体类。在外系统是人的时候,经常会有人犯这样的错误。例如以下用例规约片段:
1. 助理选择公开课,请求创建通知任务
2. 系统验证所选公开课适合创建通知任务
“助理”是执行者,映射一个边界类“助理接口”是可以的,但如果映射一个“助理”类,如图8-24,那就错了。
图8-24 外系统不映射实体类
系统是否需要一个“助理”类,要看系统是否需要维护助理的信息。如果需要,会在某个用例规约的某个地方体现,例如,可能会有一个步骤:
7. 系统保存通知任务
绑定一个字段列表:
7. 通知任务=4+创建时间+创建人
这个“创建人”就是助理,说明系统需要记住助理的信息,这时才会有“助理”类。
但并不是所有的系统都需要保留人的信息。例如,乘客坐电梯上楼,乘客是电梯系统的执行者,但电梯系统可能不需要"乘客"实体类,因为它不需要记住乘客的信息。
当然,有朝一日,电梯升级为防疫电梯,用例规约里有:
4 乘客提供身份标识
5 系统验证身份标识合法
6 系统记录乘客信息和入厢时间
这时,电梯系统里就有"乘客"实体类了,因为系统要记住乘客的信息。
当然,虽然电梯系统没有"乘客"类,但会有"乘客接口"类,可能的类图和常见的实现方式如图8-25。
图8-25 “乘客接口”及其常见的实现
8.2.2.3 关于控制类
控制类相当于用例在系统中的“代理”,它的责任是控制用例流,为实体类分配责任。如果在分配责任时发现控制类只起到传递的作用,没有起到分解和分配的作用,也可以把控制类去掉。
因为每个用例直接映射一个控制类,可以用“用例名称+控制”来为控制类命名。
因为构造型已经包含“边界类”、“控制类”的概念,严格来说,“员工接口”、“审批控制”类名后面的“接口”、“控制”可以省掉,在分析映射设计时,再通过映射套路中把它补上去。不过,考虑到可能还会有“员工”、“审批”实体类,而且很可能会一起出现在同一张序列图上,仍然保留“接口”、“控制”的尾巴看起来更顺眼一些。
8.2.2.4 关于实体类
边界类与外系统、控制类与用例的映射关系很明显,所以识别边界类和控制类不需要思考,直接按照上面的套路映射即可,甚至可以先不映射,推迟到画分析序列图时再加上去。
有的分析方法学如ICONIX提倡一种Robustness Diagram,认为可以通过它来帮助寻找类。开发人员一用确实感觉很舒服,噼里啪啦就发现好多类,有一种"我已经取得了不小成绩"的错觉,不过要是仔细看看,就知道"发现"的大多是边界类、控制类。这些类其实用不着刻意去发现,只要按照图8-20的套路映射即可。
最难的工作——寻找实体类以及它们之间的协作,Robustness Diagram却是寥寥带过,甚至容易误导建模人员把实体类和用例一一对应。所以,本书不推荐开发人员额外花时间画Robustness Diagram。
*和前文多次提到的一样,凡是不需要思考就可以得到很多“成果”的“方法”,都容易成为懒人摸鱼的遮羞布。*
建模人员的思考工作量应该花在识别实体类上。一个用例需要哪些实体类协作实现、如何协作,一个实体类会参与哪些用例的实现,这是一个多对多的映射,需要由建模人员的大脑决定哪种映射最好。
因此,本章以下内容提到的“类”,缺省意思为“实体类”。
边界类、控制类的命名有“**接口”、“**控制”的尾巴,实体类的命名就不要尾巴了,直接用核心域概念命名即可。例如,命名为“员工”而不是“员工实体”。
8.2.2.5 不存在“系统”这个类
"系统"的概念是需求工作流的概念。在需求工作流,我们把系统看作一个对外提供服务的整体。在分析工作流,"系统"的概念已经被打碎成很多个类,"系统"这个词不需要识别成类。
图8-26表达了不同工作流视角下的目标系统。
图8-26 各工作流如何称呼目标系统
在业务建模工作流,研究范围是组织,而组织中有很多系统,在业务序列图上提到目标系统时,只是说“系统”二字无法让人理解指的是哪一个系统,需要写出目标系统的名字“***系统”。
在需求工作流,研究范围是目标系统整体,此时,聚光灯已经打在目标系统身上,不需要再写目标系统的名字,写“系统”二字即可。
就像公司开表彰会,老总宣布优秀员工名单时,要说名字“罗永昊”、“罗阵宇”,轮到优秀员工罗永昊上台发言时,罗永昊称呼自己就不好再说“罗永昊”,说“我”、“在下”、“小弟”就行了。
*用自己的名字以及第三人称来称呼自己,往往代表一种极度的“自信”。例如:
“凯撒注意到这事,把他的军队撤到最近的一座山上去”(《高卢战记》)
“老胡觉得这件事实在不应该”
“婷婷想吃冰淇淋嘛”*
而在分析工作流,研究范围是系统的内部构成。系统已经被分解成很多个类,这时就不能再说“系统”了。
有的建模人员会把"系统"识别成一个类,画成这样:
图8-27 无意义的类图
这种图画出来既没有根据(你怎么知道是这样分解?)又含义模糊(模块指的是需求包还是组件?),对剖析系统的复杂性没有帮助,但给建模人员带来一种虚假的成就感:我描述了几个类之间的关系,而且还是组合关联(还可以联想到高大上的“组合优于继承”),已经开始剖析系统的复杂性了呢,甚至还可以扯上领域驱动设计话语中的“聚合根”——最妙的是,不用思考,信手拈来!