层次化
升级escalation和降级demotion,把设计中的循环依赖部分移到物理层次的不同级别上。不透明指针opaque pointer和哑数据dumb data可用来消除概念上的依赖关系的物理隐含。冗余redundancy和回调callback防止不必要的物理依赖。
- 原则:允许两个组件通过#include指令彼此“知道”隐含了循环物理依赖。
- 定义:如果一个子系统可编译,并且单个组件(包括.c文件)的包含指令隐含的依赖图是非循环的,则称这个子系统是可层次化的。
- 原则:相关抽象接口中的内在耦合使他们更抗拒层次分解。
升级
- 定义: 如果组件y处于比组件x更高的层次上,并且y在物理上依赖x,则称组件y支配dominate组件x。
支配的重要性在于它能够提供简单的层次号之外的额外信息。- 原则:如果同层次的组件时循环依赖的,那么就有可能把相互依赖的功能从一个组件升级为一个潜在地新的更高层次的组件(依赖于每一个初始的组件)的静态成员。
- 原则:在庞大的低层次子系统中的循环物理依赖将最大程度地增加维护系统的总开销。
总结:可以通过把相互依赖升级到更高的层次将循环依赖转换成受欢迎的向下依赖。通过避免子系统本身内部组件之间不必要的依赖,可以显著降低子系统和它所有客户程序的维护开销。
降级
把公用的功能推到物理层次结构的更低层的技术
- 原则:如果同一层次的组件是循环依赖的,那么就有可能把相互依赖的功能从每一个组件降低到一个潜在地新的较低级(共享)组件中,每一个原来的组件都依赖于这个新组件。
- 原则: 将共有代码降级可促成独立重用。
- 原则:可以将升级策略与基础设施降级结合起来,以增强独立重用。
- 原则:把一个工具类分解为两包含更高和更低层次功能的类可以促进层次化。
- 原则:将一个抽象的基类分解成两个类--一个定义一个纯粹的接口,另一个定义它的部分的实现,可以促进层次化。
- 原则: 把一个系统分解成更小的组件,既可以使它更灵活,也可以使它更复杂,因为现在要与更多的物理部件一起工作了。
总结:我们有时可以通过分解出普遍需要的功能,并将它移到物理层次结构的更低层来消除组件之间的相互依赖。降级不仅对改进循环相互依赖设计有用,而且也对减少非循环体系结构的CCD有用。将共有子系统降级可同时改进可维护性和可扩展性。
不透明指针
- 定义:如果编译函数f的函数体时要求提前看到类型T的定义,则称函数f实质(in size)使用了类型T。
- 定义:如果编译函数f以及f可能依赖的任何组件时,不要求提前看到类型T的定义,则称函数f只在名称上(in name only)使用了类型T。
- 定义:如果编译组件c时要求必须提前看到类型T的定义,则称组件c实质使用了类型T。
- 定义:如果编译组件c以及c可能依赖的任何组件时不要求提前看到类型T的定义,则称组件c只在名称上使用了类型T。
- 原则:只在名称上使用了对象的组件可以独立于被命名的对象被彻底测试。
如果一个指针所指向的类型的定义不包含在当前的编译单元中,这个指针就被称为是不透明的(opaque)。- 原则:如果一个被包含的对象拥有一个指向他的容器的指针,并且要实现那些实质地依赖那个容器的功能,那么我们可以通过以下方法来消除相互依赖;(1)让被包含类中的指针不透明;(2)在被包含类的公共接口上提供对容器指针的访问;(3)将被包含类的受影响的方法升级为容器类的静态成员。
哑数据
- 原则:哑数据可以用来打破in-name-only依赖,促进易测试性和减少实现的大小。但是,不透明指针可以同时保持类型安全和封装;而哑数据通常是不能的。
冗余
- 原则: 于一些重用形式相关的额外耦合可能会超过从该重用获得的利益。
- 原则: 提供少量的冗余数据可以使对一个对象的使用只在名称上,从而消除连接到那个对象该类型的定义的开销。
- 原则:将子系统打包,使其连接到其他子系统的开销最小化,这是一个设计目标。
回调
- 原则: 不加选择地使用回调函数可能导致难以理解、调试和维护的设计。
- 原则: 对回调的需求可能是不良的整体体系结构的一个症状。
管理类
- 原则:建立较低层次对象的分等级的所有权,可以使一个系统更容易理解和更可维护。
总结:建议协同操作对象的清晰的所有权对好的设计来说是基本的。如果两个或更多的对象共享共有的所有权,那个功能应该升级到一个管理类。
分解
分解的意思是可以提取小块的内聚功能,并把它们移动到一个较低的层次,以便他们可以被独立的测试和重用。
- 原则: 将独立可测试实现细节分解出来并降级,能够减少维护一个循环依赖类集合的开销。
- 原则: 在循环物理依赖不可避免的地方,将其升级到尽可能高的层次可减少CCD,升值可以使循环能够给由一个单个的,大小便于管理的组件代替。
- 原则:授权友元关系不会产生依赖,但是为了保持封装可能会引起物理耦合。
总结:分解是一种通用技术,可用来减少有着固有循环依赖的设计的维护开销。通过将一些实现复杂事务重新安装到较低层次组件中,可以独立于余下的循环相互依赖代码对该功能进行测试(并可能重用)。一般的分解会得到更加灵活的体系结构,又不会牺牲运行效率。然而,在分解一个子系统的接口时,客户可能被要求使用子系统层次结构中较低层次的组件接口。
升级封装
将大量有用的低层次类藏在一个单个组件的接口后面,这样的组件是包装器(Wrapper)
- 原则: 什么是和什么不是实现细节决定于物理层次结构内部的抽象级别。
- 原则: 将封装所在的层次升级,能够消除对一个子系统内协同操作的组件授予私有访问权限的需求。
- 原则:私有的头文件不是适当的封装替代品,因为它们禁止并排(side-by-side)重用。
- 定义:在层次系统中,封装一个类型(该类型定义在头文件内的文件作用域中)意味着隐藏了它的使用而不是隐藏了类型本身。
- 原则:包装器组件可以用来封装一个子系统内的实现类型的使用,但允许其他类型通过它的接口。
总结:试图按"每个组件"的原则来封装一个子系统的实现,可能会妨碍低层次的通信和/或破坏一个其他的可行设计。比限制单独类中的客户可访问功能更好的是,我们可以限制在总的子系统接口中暴露给用户类的子集。通过使用一个包装器组件,我们可以将封装的层次升级到子系统的最高层,这样可以消除对低层次友元关系的需求,从而也消除了将紧密耦合的类合并成一个单个的超大型组件的必要。
浙公网安备 33010602011771号