层次化的主要目的还是为了设计出高质量的系统
+ 易于理解
+ 易于维护
+ 易于测试
+ 易于重用
= 少加班
导致物理循环依赖的原因
产生物理循环依赖的原因是多种多样的,书中列举了3种,但总结一下可能2种更合适一些。
- 心理方面:在对原有系统开发新功能时,或修改问题时,信手拈来的习惯可能在不经意间造成循环依赖。
- 技术方面:设计系统逻辑关系时,逻辑上的耦合导致了物理上的循环依赖。
处理物理循环依赖的方式
升级
组件间支配: 如果组件y处于组件x的上层,并且y在物理上依赖于x,则称组件y支配组件x。
支配的重要性在于它能够提供除了层次号以外的其他的信息。
- 一个高层组件增加了一个对底层组件的依赖不会产生循环依赖;
- 同一层的两个组件间增加一个依赖会影响层次关系,但也不会产生循环依赖。
升级:如果一个高层组件和一个底层组件间有循环依赖,可以考虑把依赖的这部分功能转移到高层组件中,从而打破依赖关系。这种方式称为升级。
升级的两种思路:
将部分功能转移,或者构造一个上层模块。
降级
与升级相对应的便是降级,有些功能模块是对于整个系统比较公共的,考虑把它们放入底层组件中可以提高共享,复用的程度,还是值得尝试的。
降级很好理解,但有时在没有发生循环依赖时也可以考虑使用降级方式优化结构,比如当发现有部分代码重复时,就可以把重复部分封装起来,降级处理,来提高复用程度。
升级和降级并是相对的,并不是对立或互斥的,有时结合使用可以达到更好的效果。
关于降级,书中还有一些小的建议,可以参考
- 将一个具体类分解为包含更高和更低层功能的类可以促进层次化
- 将一个抽象基类分解为两个类 —- 抽象接口和实现可以促进层次化
- 把一个系统分解成更小的组件即可以使它更灵活,也会使它更复杂。
不透明指针
不透明的定义:如果一个指针指向的类型定义不包含在当前的编译单元中,则说它是不透明的。
程序解释最简单:
Class A在不知道B为何物时直接使用了B,这个是经常用到的。
A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
public:
func(B* pObjB);
}
#endif
这时候A对于B产生的只是名称上的依赖。并没有物理上的依赖,没有头文件引用等。
哑数据
哑数据是对不透明指针的一种概括,它主要指一个对象拥有不知道如何解释的信息,这些数据一般会用在另一个对象的上下文中。或者说它是不透明指针的一种泛化。
一个简单的运动会的例子(比赛场地,运动员和比赛项目)
运动员需要知道在哪块场地进行比赛,比赛场地需要知道有多少运动员参赛,但真的有必要让运动员知道场地的长宽高,有多少照明灯等具体信息吗?场地其实也没有必要知道运动员的身高体重等具体信息。
这里就没有必要在运动员和场地间互相定义不透明指针。在比赛场地类中用一个数组保存运动员ID就完全可以搞定。同样,运动员也保留一个份比赛场地ID的List也可以找到相应的信息了。
这里把运动员和比赛场地类型的不透明指针泛化成ID的形式。这些ID单纯对于运动员和比赛场地是没有任何意义的,但放在上下文中它们便是不可缺少的信息。
泛化提供了解除依赖(名称和物理)的一种方式,同样它也没有办法提供更具体的信息和安全的类型转换了。
冗余
冗余比较好理解,有复用的地方一定会带来一定的耦合关系,当耦合带来的问题远远大于复用带来的好处时此时就要考虑一下是不是真的需要复用。
也许这时直接复制一份代码在当前的模块中比直接复用组件更简单和更容易维护。
回调
回调函数本身是C/C++的一种语法,这里的回调主要是在设计层面上的。它是指由客户提供回调函数的接口,然后由子系统在上下文中进行调用。这样子系统便可以控制客户组件的某些行为。
可以考虑一个简单的消息驱动回调函数例子,在两个组件间只是一种消息传递的关系,而并没有其他的耦合。
管理类
在设计类时功能单一的原则可以使类方便的复用,而且很容易维护,但同样也会带来一些问题,比如一个子系统使用了很多功能单一的类的时候,这时会产生很多的依赖关系,并不利于管理。
例如一个画图组件,我们可以画圆,画方,画线,此时完全可以定义一个管理类叫画图,它对内包含三种画图的类,对外提供接口,此时这个画图的管理类变成对外依赖的接口。
这是使用管理类解除依赖关系一种方式。另一个好处是管理类本身还有协调的功能,因为它的层次更高,可以看到更多的状态变化。
分解
分解的主要思想是提取小块高内聚的功能,封装并转移到下层组件中以便提供功能复用。
分解与降级类似,区别是分解不是必然的会消除循环,他只是减小参与循环中的数量。
升级封装
封装本身是面向对象中的一个概念,屏蔽实现细节,使用户不能访问。
在系统设计时让每个组件都暴露接口给客户编程使用,在一定程度上是一种便利,但有时也会带来混乱。
比较子系统 A和B
A中所有的组件都对外开发,提供接口,B中暴露在外的只有w,x,z。
当系统规模较大时,A中每个组件暴露的接口未必都能被高效的使用,甚至可能找都找不到。 B中的封装的方式也许能够提供一个相对清晰的结构。
总结:
关于书中提出的各个原则和设计方法,没有灵丹妙药。这里更多的思考是对于不同系统不同问题的折中的解决方法,趋利避害,使系统在当前的运行环境中更加的合理。脱离实际环境,实际问题很难讲哪种方法就是最好的,没有任何副作用的。
版权声明:本文为博主原创文章,未经博主允许不得转载。