8.3 建模步骤C-2 识别类的关系
8.3.2 识别泛化关系
8.3.2.1 识别泛化的思路
(3)自上而下(从一般到特殊)
如图8-92所示,这个识别思路就是8.2.5.6 属性是否对所有对象都有意义里的思路。
图8-92 自上而下-一个类分裂出子类
和上面的“自下而上”一样,这里的属性也包括关联,如图8-93。
图8-93 关联只对部分对象有意义时分出子类
不一定像图8-93那样关联到不同的类。关联到同一个类但角色名不同,而某个角色只对部分对象有意义,也可以考虑分出子类,如图8-94。
图8-94 某个角色只对部分对象有意义时分出子类
8.3.2.2 警惕拼凑泛化
您可能注意到,以上我们尽量通过属性(包括关联)来解释泛化关系。
虽然泛化带来的好处是落在行为上,但如果抛开属性直扑行为,很可能会带来“伪泛化”、“伪面向对象”。
如图8-95,因为X和Y都有操作op1,所以泛化出A,把op1提上去成为抽象操作。这个没有问题。
问题出在前面,怎么知道op1作为X和Y的操作是合适的?最终的依据还是X和Y的属性(包括关联)。
图8-95 操作怎么来的,需要有依据
有一种偷懒遮羞布,就是胡乱安排操作,然后拼凑出泛化关系,根本不顾安排的操作是否合理。
一些伪面向对象实践经常得到各种名称的最后是or或er(汉语则为“器”)的类。
开发人员可能一开始按照面向过程的思路噼里啪啦把代码写出来,然后出于赶时髦需要“面向对象”,他就把过程名称加上or或er作为类名称,然后把原来的过程作为or或er类的操作。
当存在多个类似过程时,还可以加上一些泛化关系(或接口-实现)来做点缀,如图8-96,这样看起来就更有“面向对象”的味道了。
图8-96 带有泛化关系的or或er类
可能在某些开发人员眼里,图8-96很有格调,可以用来吹嘘的高大上词汇有:OCP(开放-关闭原则)、DIP(依赖倒置原则)、模板方法模式……等。这些由泛化关系衍生出来,被网红圈子广泛吹嘘的原则和模式作用十分有限,指望了解被包装出来的“SOLID原则”之类就能应对软件复杂性,那真是太天真了。
★关于Robert C. Martin和他的书《敏捷软件开发-原则、方法与实践》的评价,参见本书2024版第1章关于“敏捷”染色的阐述。
or或er类往往没有属性,只有操作,大量的逻辑仍然隐藏在子类操作的实现中。当然,开发人员也可能觉得这是好事,“这说明我有算法啊!”,好像很高大上,但这所谓的“算法”(其实是核心域逻辑)正是建模的重点,结果被开发人员完美地遮掩过去了。
和其他的偷懒遮羞布类似,这种or或er的做法一一对应,思考工作量小,还有各种高大上词汇护法,于是开发人员洋洋得意,感觉自己已经很厉害了,连称“受用”,纷纷去拥抱这种偷懒遮羞布。
or或er类有时会使用“策略模式”作为伪装,如图8-97,哇,我可以灵活组装各种策略!顺便再吹一通“组合优于继承”之类,其实还是换汤不换药,核心域逻辑仍然隐藏在子类操作的实现中。那些“策略”才是建模的重点,同样被开发人员完美地遮掩过去了。
图8-97 “策略模式”换汤不换药
如果一个类的命名中有类似这些内容:er、or、器、策略、Strategy、Policy、规则、Rule、算法、Algorithm……然后这个类或其子类的操作中有长长的“算法”,那么应该思考一下,长长的“算法”中到底定义了哪些变量?哪个部分的代码最复杂?
背后往往就是候选的实体类以及需要封装的操作,尽量分离出实体类,把各种逻辑尽量封装在实体类的操作中。这样的思考更辛苦,但也更有价值。
有心的读者可以仔细观察一下你接触到的领域驱动设计相关的文章,看看有没有这样的现象:
全篇文章没有剖析复杂一点的逻辑,但会有“**策略”、“**规则”这样的类或者组件存在。莫非这些“**策略”、“**规则”是别人已经做好的,他拿来就用?追问下去,多半不是,而是他要负责解决的问题。
你猜我怎么知道是这样?因为我接触的开发团队和开发人员太多了,追问下去,脓包破裂的概率极高。