最近在看《Java應用架構設計 模塊化模式與OSGi》,深有感觸,在此做些總結。(電子版可以在Java1234.com上下載到)
在使用Java開發中,各種依賴不可避免。比如類之間的繼承,jar包之間的相互依賴。依賴在某種程度上不可避免,但是過多的依賴勢必會增加系統的復雜性,使代碼難以閱讀,從而成為團隊開發的阻礙。循環依賴尤其糟糕。
循環依賴存在與多種實體之間,尤其是類、包、模塊之間。當兩個類相互引用時,就會出現循環依賴。下面摘抄書中的一個例子。
如圖1.1所示,有Customer和Bill兩個類。在本例中,Customer有一個Bill的實例列表,而Bill實例也引用Customer來計算折扣總額。這也成為雙向關聯(bidirectional association)。對於維護和測試,這將是一個將是一個問題,因為在不引用另一個類的情況下,你不能單獨的對其中一個類做任何事情。
圖1.1 類之間的循環依賴
代碼清單1.1展示了Customer類,代碼清單1.2展示了Bill類。(為了簡化,每個類的特定部分進行了省略。)在這里清楚展示了循環依賴。
代碼清單1.1 Customerpackage 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.2 Billpackage 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所示。現在,借助mock的DiscountCaculator,Bill就可以容易的進行(單元)測試了。當然,測試Customer依舊需要Bill的參與。單着不是循環的問題了,這里暫時不做討論。很顯然,引入DiscountCalculator打破了Customer和Bill類之間依賴。但是,它能打破所有的循環依賴嗎,包括可能存在與模塊之間的?
圖1.2 打破循環
代碼清單1.3展示了修改后的Customer類,它實現了DiscountCalculator接口,改接口如程序清單4.4所示。
代碼清單1.3 修改后的Customerpackage 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.4 DiscountCalculatorpackage com.scott.bill; import java.math.BigDecimal; public interface DiscountCalculator { public BigDecimal getDisCountAmount(); }