1.4.2 抽象设计的评判标准
类设计是一个增量迭代的过程。坦白地说,除了那些最不重要的抽象设计,我们从来没有在第一次就完全正确地定义一个类。对于最初的抽象设计,需要花一些时间来琢磨它粗糙的概念边界。当然,优化这些抽象设计是有代价的,包括系统的重新设计、系统设计的可理解性和设计结构的完整性等方面,因此我们希望在一开始就尽量正确。
怎样才能知道某个类的抽象设计是否良好呢?我们可以通过它的耦合性、内聚性、充分性和完整性4个指标来度量。
(1)耦合性:强耦合使系统变得复杂,因为如果某个模块与其他模块过度相关,它就难以独立地被理解、变化或修正,通过降低耦合性,可以降低复杂性。在耦合和继承的概念之间存在着矛盾关系,继承引入了严重的耦合。一方面,我们希望类之间弱耦合;另一方面,继承又能帮助我们处理抽象之间的共性。我们通常说“组合优于继承”,正是因为继承的耦合性比较强。鉴于此,有些编程语言(比如Go语言)就直接取消了继承。
(2)内聚性:内聚测量了单个模块(类、包、组件)内各个元素的联系程度。我们最不希望出现偶然性内聚,即完全无关的抽象被塞进同一个类或模块中。例如,考虑由狗和航天飞机的抽象组成的一个类。我们最希望出现功能性内聚,即一个类或模块的各元素一同工作,提供某种清晰界定的行为。如果Cow类的语义包含了一头牛的行为——完全是牛,只有牛而没有其他,那么它就是功能性内聚。
(3)充分性:所谓充分,是指类或模块应该记录某个抽象设计足够多的特征,从而允许有意义的交互,否则将使组件变得无用。例如,如果我们设计Set(集合)类,应该包含从集合中添加、删除元素的操作,如果忘记设置这些操作,那么这个Set类的功能就是不充分的。好在只要我们构建一个必须使用这种抽象的客户,这种问题很早就会被发现。
(4)完整性:完整是指类或模块的接口记录了某个抽象全部有意义的特征。充分性意味着最小的接口,但一个完整的接口意味着该接口包含了某个抽象的所有反向。完整性是一种主观判断,我们有可能做过头。为某个抽象提供全部有意义的操作会让用户不知所措,通常也是不必要的,因为许多高级操作可以由低级操作组合得到。例如,向集合里添加4个元素的操作就是不必要的接口,因为可以通过基础的Add操作得到同样的效果。