最近在看《Java应用架构设计 模块化模式与OSGi》,深有感触,在此做些总结。(电子版可以在Java1234.com上下载到)
在使用Java开发中,各种依赖不可避免。比如类之间的继承,jar包之间的相互依赖。依赖在某种程度上不可避免,但是过多的依赖势必会增加系统的复杂性,使代码难以阅读,从而成为团队开发的阻碍。循环依赖尤其糟糕。
循环依赖存在与多种实体之间,尤其是类、包、模块之间。当两个类相互引用时,就会出现循环依赖。下面摘抄书中的一个例子。
如图1.1所示,有Customer和Bill两个类。在本例中,Customer有一个Bill的实例列表,而Bill实例也引用Customer来计算折扣总额。这也成为双向关联(bidirectional association)。对于维护和测试,这将是一个将是一个问题,因为在不引用另一个类的情况下,你不能单独的对其中一个类做任何事情。
图1.1 类之间的循环依赖
代码清单1.1展示了Customer类,代码清单1.2展示了Bill类。(为了简化,每个类的特定部分进行了省略。)在这里清楚展示了循环依赖。
package com.scott.cust; import java.util.*; import java.math.BigDecimal; import com.scott.bill.*; public class Customer { private List<Bill> bills; //特定Customer的折扣根据订单数目计算 public BigDecimal getDiscountAmount() { if (bills.size() > 5) { return new BigDecimal(0.1); } else { return new BigDecimal(0.03); } } public void createBill() { Bill bill = new Bill(this); if (bills == null) { bills = new ArrayList<Bill>(); } bills.add(bill); } }代码清单1.1 Customer
package com.scott.bill; import com.scott.cust.*; import java.math.BigDecimal; public class Bill { private Customer customer; public Bill(Customer customer) { this.customer = customer; } public BigDecimal pay() { BigDecimal discount = new BigDecimal(1),subtract( this.customer.getDiscountAmount()).setScale(2, BigDecimal.ROUND_HALF_UP); //确认折扣和应付款代码省略 return paidAmount; } }代码清单1.2 Bill
可以有多种方式打破循环依赖(笔者目前所知就是引入抽象),其中之一就是引入抽象,如图1.2所示。现在,借助mock的DiscountCaculator,Bill就可以容易的进行(单元)测试了。当然,测试Customer依旧需要Bill的参与。单着不是循环的问题了,这里暂时不做讨论。很显然,引入DiscountCalculator打破了Customer和Bill类之间依赖。但是,它能打破所有的循环依赖吗,包括可能存在与模块之间的?
图1.2 打破循环
代码清单1.3展示了修改后的Customer类,它实现了DiscountCalculator接口,改接口如程序清单4.4所示。
package com.scott.cust; import java.util.*; import java.math.BigDecimal; import com.scott.bill.*; public class Customer implements DiscountCalculator { private List<Bill> bills; //特定Customer的折扣根据订单数目计算 public BigDecimal getDiscountAmount() { if (bills.size() > 5) { return new BigDecimal(0.1); } else { return new BigDecimal(0.03); } } public List<Bill> getBills() { return this.bills; } public void createBill() { Bill bill = new Bill(this); if (bills == null) { bills = new ArrayList<Bill>(); } bills.add(bill); } }代码清单1.3 修改后的Customer
package com.scott.bill; import java.math.BigDecimal; public interface DiscountCalculator { public BigDecimal getDisCountAmount(); }代码清单1.4 DiscountCalculator