模块松耦合模式

模块之间紧耦合是一件坏事情,无论你是否使用OSGI这些模块化技术,在套入模块技术之前,你必须从设计高度来降低模块之间的耦合。

Fun With Modules一文提出了几种模块松耦合方法,最佳事件或称为模式吧:Escalation Demotion 和Callback。

该文以Customer和Bill这个简单案例为例子,其模型类图如下:

将这两个模型及其相关类打包到两个Jar包中:cust.jar 和 bill.jar
使用JarAnalyzer 这个工具可以帮助我们分析出Jar包之间的依赖关系,如下图:

从图中可以看出,这两个包其实是存在循环相互依赖。

Escalation
升级抽象,将设计提高一个层次来降低依赖,解决上面案例的循环依赖,还需要从为什么产生循环依赖这个原因下手:

一个Customer有多个Bill实例. 当Bill的支付方法被调用时,Bill需要决定是否使用折扣, 但是折扣率是Bill的一个特性字段,不是Bill的. 这样,Bill类就要调用Customer的方法来决定折扣率。

但是从业务讲,也不能将折扣率从Customer移植到Bill中,因为不同客户确实有不同折扣率,是其自身一个特性。

那么,为了截断这种循环依赖,我们建立一个类为CustomerMediator,这个中介者mediator封装了折扣的计算,然后将结果递交给Bill类,结果如下:

mediator.jar被直接和客户端打交道,升级到cust.jar和bill.jar之上了。

Demotion
升级是一种办法,反过来降级也是一种解耦方式,在上面案例中,我们引入一个DiscountCalculator ,如下类图:

Customer可以作为DiscountCalculator的工厂,因为它有DiscountCalculator输入值折扣率。
这样,我们得到如下模块依赖图:

大家会发现,在这个案例中降级办法比较合适,无论升级还是降级,合适办法取决于业务场景上下文。

待续...

Callback
回调类似观察者模式. 我们将DiscountCalculator类重构到接口,然后修改Customer实现这个接口,如下:

这样,我们就不必把Customer传入Bill,而是替代以这个接口DiscountCalculator ,这和降级模式区别是,这次我们可以将DiscountCalculator 打包到Bill.jar中,降级是将DiscountCalculator专门作为一个单独的包。

下图是Jar包依赖,比较清晰简单了:

Inverting Relationships
反向关系模式,上面Callback模式是比较简单的一个解决办法,但是如果我们只用 cust.jar,不用Bill.jar,因为Cusomter的接口被打在了Bill.jar中,这就无法实现。

这时我们可以使用反向关系来实现,如下图:

这时,把Bill重构为一个接口,然后把它打包到cust.jar中,好像有点弄虚作假:

Eliminating Relationships
消除关系模式,使用反向模式前,我们可以单独测试Bill.jar,使用后,可以单独测试Cust.jar了,但是如果我们需要两个都可以单独测试呢?

因为已经使用了反向模式,实际上抽象了关系,这时我们只需要把Bill 和 DiscountCalculator两个接口打包成一个单独包教Base,这样,Cust.jar和Bill.jar都依赖这个Base.jar,不相互依赖,就可以单独测试了。

作者还写了一篇The Use/Reuse Paradox(用和重用的悖论),不错,有机会我大意翻译一下。

个人心得:在Jdon框架实践中也会碰到循环依赖的问题,因为Jdon框架使用的是构造器注射,如下:


Class A{
B b;

public A(B b){
this.b = b;
}

}

B是通过A的构造器注射到A中的,在复杂系统中,比如JiveJdon,有时B会依赖C,而C会再依赖A,本来是A依赖B,现在变成双向B又依赖A了,循环依赖,这是运行时会报依赖错误。

因为我们一个类是标记以@Component,组件的意思,组件和类是集合,所以,碰到这种循环依赖,我基本也是采取上述模式,找出循环依赖的原因,把那段代码拿出来单独作为一个@Component,这样,能够保证类的纯正性,减少边际影响也就是副作用吧。

换换句话说,你一个类中功能太全,当然就容易产生外界很多依赖,依赖一圈后,弄不好绕到自己头上。

减少依赖,也是用好DI autowired的一个要诀。
[该贴被banq于2010-01-14 14:11修改过]

请问Banq老师,升级与降级怎么区分呢?

2010年01月20日 21:00 "freeren"的内容
升级与降级怎么区分

这其实两个相反过程,一个是提升 提拔,一个是降职,根据它的功能特点吧,如果比较重要,影响全局,就升级,如果只是一个功能工具运算,一个算法实现,就降级处理。

实际执行尺寸还是依据What和How标准,将How怎么做的降级,将表达是什么 做什么的方向目标东西升级。

把解决模块之间的藕合提升为模式是非常的棒!
依据我的经验把这种模式使用场景归纳为:模块之间,层与层之间,以及DI容器中的循环依赖之间。