8.1.6.6 属性要直接描述类
类和属性连在一起说"类的属性",应该能直接说得通,否则类和属性的搭配是不合适的。这个时候应该找到或建立合适的类,把该属性移进去。例如图8-53,“联系人的组织名称”中间隔了个“组织”,不能直接说通,需要添加一个“组织”类,把“名称”挪过去。
图8-53 属性要能直接描述类
在缺少抽象的“照猫画虎”式建模中,这种错误比较常见。例如,一张工作居住证上确实有该人员聘用单位名称。建模人员对照工作居住证,一项一项把它搬到类图上的类中。
如果确定每个联系人只就职于一个组织,而且系统只关注组织的名称,可以将“名称”合并到“联系人”成为一个属性“组织名称”,如图8-54。不过,如果以上的假设发生变化,这样的做法应变成本很高。
图8-54 特定条件下可以简化
要特别说明的是,习惯于关系数据库建模的建模人员有时会犯这样的错误,在一个类里放上另外一个类的属性作为“外键”。比如针对上面的例子,建模人员会想:“联系人”里放“组织名称”确实不合适,但是放个“组织编码”作为外键总可以吧?其实也不可以。"组织编码"是“组织”的属性,是封装在“组织”中的秘密,“联系人”不应该拥有“组织”的属性,它拥有的是“组织”对象,通过访问“组织”对象公开的操作间接访问“组织”的属性。
图8-55 不需要外键属性
在设计工作流,需要把类图映射到关系数据库时,确实需要把"组织"表的主键(可能不是"编码"而是系统生成的代理主键)放在"联系人"表中作为外键,但正如前文所说,这是另一个领域的知识,现在不需要理会。
下面我们用这条审查原则检查一下UMLChina系统的例子:
(1)用例规约中提到“联系人当前所在城市所属分区与公开课举办城市所属分区相同”——“联系人”的“城市”的“分区”,需要如图8-56分解。
图8-56 分离不直接描述类的属性——UMLChina系统例1
(2)用例规约中“通知任务的创建人和创建时间”。实际上是“通知任务”的“创建事件”的“时间”、“通知任务”的“创建事件”的“创建人”,而这个创建人就是公司助理。此时应分解为三个类,考虑到一个通知任务只有一个创建事件和它关联,而且不考虑创建事件的其他属性,可以把“创建”合并到“通知任务”中,如图8-57。
图8-57 分离不直接描述类的属性——UMLChina系统例2
有时,属性不直接描述类的情况比较隐蔽。如图8-52中的“公开课”类,说“公开课的主题”,“公开课的大纲”是可以的,但是实例化后会发现,很多“公开课”对象的“主题”和“大纲”是一样的,不同“公开课”对象的不同属性值主要体现在“开始日期”、“结束日期”和“城市”上。当发现针对一些属性,有很多对象的值相同,而且这些属性刚好组成了一个领域概念,应该分离出这个概念。如图8-58,可以分离出“课程”,把这两个属性移到“课程”中。
图8-58 分离有大量相同值的属性到另一个类——UMLChina系统例子3
这里经常会有异议,认为“公开课”也要有“主题”和“大纲”属性,因为一门课程的主题和大纲会随时间变化,我们需要像快照一样,复制课程当时的属性值,记住在某时某地举办公开课时的主题和大纲,否则维护的信息是不真实的。如果按照这样的思路,和公开课关联的所有类的属性都需要复制到公开课中,因为联系人的姓名、城市的名称也会改变。再推下去,就需要一个巨大的类,把所有通过关联线连在一起的类的所有属性组合在一起。
应对这种情况的一种做法是针对特别需要关注的视图另外加报表类,例如“公开课通知”,这个报表类和“公开课”、“课程”不关联,而且对象的值不会变化。当然,如果一开始没有创建某个视图的报表,对象的值发生变化后,想从其他视图推导出该视图不一定行得通。
如图8-59所示,报表ReportA、ReportB、ReportC、ReportD记录了某个时间点A、B、C不同属性组合的情况。假设现在需要一种新的报表,里面包含属性a2,想要追溯某个时间点上a2相关的记录已经是不可能的。因为a2已经变化,其他报表没有保存下来。
图8-59 从不同的类提取属性组成报表
但这样的情况也是正常的,系统仅仅需要映射领域的一小部分知识。比起记住“某个姓名曾经在某个城市名称上过课“来说,记住“某人曾经在某个城市上过课”更加本质。
如果要更充分地记录历史,可以针对“课程的主题和大纲发生变化”这个领域事实建模,也就是说,为对象建立不同的版本,或者记录对象所有的属性值变化,如图8-60。
图8-60 跟踪对象的属性值变化
(3)如图8-61所示,“发件邮箱”的“最小时间间隔”。这个“最小时间间隔”在很多“发件邮箱”对象中值相同,说明它其实不直接和具体每个发件邮箱相关,而是和发件邮箱的规格相关。例如,如果我们设定,针对163.com免费邮箱,发送邮件的最小时间间隔为70秒,那么就算注册了100个163.com免费邮箱,这个值都是一样的。同样,SMTP服务器、POP3服务器……等属性的值,也只和发件邮箱的规格相关,和具体的每个发件邮箱无关。应该分离出“发件邮箱规格”,和“发件邮箱”关联。
8-61 分离有大量相同值的属性到另一个类——UMLChina系统例子4
8.1.6.7 属性在本领域内不可以再分解
如果属性再分解就得到了其他领域的概念,那么这个属性可以留在类中。如果可以继续分解成本领域的概念,可以考虑把这个属性独立出去变成另一个类。
如图8-62中,"联系人"的"称呼"属性的类型是String。String属于基础语义领域,已经不属于人员管理领域,那么"称呼"可以留在“联系人”中作为属性存在,而"组织"还可以像右侧所示分解成“名称”、“办公地址”等,这些概念依然属于人员管理领域,所以可以考虑将“组织”分离为一个类,“联系人”关联到“组织”。
图8-62 分离可以在本领域内分解的属性
注意:分离或不分离的理由是“是否另一个领域”而不是“是否简单”。就拿“称呼”分离到String来说,String其实不简单。以.NET Framework 4.5的实现为例,其中的String类有123个操作,远远超过人员管理领域某个类所拥有的操作。
感谢纠错
爱你爱我,爱你爱我,我们爱这个错
《我们爱这个错》;词:林隆璇,曲:林隆璇,唱:张信哲;1989
[1] 《软件方法》上册第六章已讨论过开发人员话语中“技术”一词的狭隘。
[2]当然,可以以Office为开发工具做二次开发,但此时使用的系统已经不是Office而是核心域的应用系统。
[3] ( a、b、c都大于时成立)
[4] C#有一种实现套路是直接写Property,后文再评述这样的实现。
[5]有人提出一种改进的脑补方法——结对脑补,美其名曰“结对编程”。
[6]此处与《软件方法(上)》说法矛盾,将修改《软件方法(上)》中执行者相关内容。