原文連接java
將全部的內容鏈接在一塊兒時應用開發的一個單調乏味的部分。有幾種方式來將數據、服務、presetntation類鏈接到一塊兒。爲了對比這些方法,我將爲披薩訂購網站編寫帳單代碼:git
public interface BillingService { // 嘗試在信用卡中扣除訂單的費用。成功和失敗的交易都會被記錄 Receipt chargeOrder(PizzaOrder order, CreditCard creditCard); }
伴隨着實現,咱們將爲咱們的代碼編寫單元測試。在測試中,咱們須要一個FakeCreditCardProcessor
來避免從真實的信用卡扣費!github
如下是,當咱們只是new
一個信用卡處理器和一個交易日誌時,代碼的樣子:設計模式
public class RealBillingService implements BillingService { public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { CreditCardProcessor processor = new PaypalCreditCardProcessor(); TransactionLog transactionLog = new DatabaseTransactionLog(); try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } } }
該代碼給模塊化和可測試性帶來問題。對真實信用卡處理器的直接編譯時依賴意味着測試代碼將從信用卡中扣費。當發生扣費被拒絕或者當服務不可用的事情時,對測試是很不方便的。框架
工廠類能夠解耦客戶端代碼和實現類。一個簡單工廠使用靜態方法來獲取和設置接口的模式實現。一個工廠使用一些樣板代碼實現:ide
public class CreditCardProcessorFactory { private static CreditCardProcessor instance; public static void setInstance(CreditCardProcessor processor) { instance = processor; } public static CreditCardProcessor getInstance() { if (instance == null) { return new SquareCreditCardProcessor(); } return instance; } }
在咱們的客戶端代碼中,咱們只是用工廠查找代替了調用new
:模塊化
public class RealBillingService implements BillingService { public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { CreditCardProcessor processor = CreditCardProcessorFactory.getInstance(); TransactionLog transactionLog = TransactionLogFactory.getInstance(); try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } } }
工廠使得編寫一個正確的單元測試成爲可能:函數
public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100); private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog(); private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); @Override public void setUp() { TransactionLogFactory.setInstance(transactionLog); CreditCardProcessorFactory.setInstance(processor); } @Override public void tearDown() { TransactionLogFactory.setInstance(null); CreditCardProcessorFactory.setInstance(null); } public void testSuccessfulCharge() { RealBillingService billingService = new RealBillingService(); Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge()); assertEquals(100, receipt.getAmountOfCharge()); assertEquals(creditCard, processor.getCardOfOnlyCharge()); assertEquals(100, processor.getAmountOfOnlyCharge()); assertTrue(transactionLog.wasSuccessLogged()); } }
上面的代碼是笨拙的。一個全局變量持有模擬實現,因此咱們須要關心設置和清理模擬實現的操做。若是撕除失敗,那個全局變量將會繼續指向咱們的測試實列。這可能會卻是其餘的測試出現問題。它還阻止咱們並行運行多個測試。單元測試
可是最大的問題是依賴關係被隱藏在了代碼中。若是咱們在CreditCardFraudTracker
上新增一個依賴項,那麼咱們不得不從新運行測試來找出哪一個依賴關係被破環了。若是咱們忘了爲正常服務,咱們在嘗試扣費前是不會發現這個錯誤的。隨着應用的增加,維護這些工廠會變得愈來愈耗費生產力。
質量問題會被QA和功能測試發現。那或許就足夠了,可是咱們無疑能夠作的更好。測試
像工廠模式同樣,依賴注入只是一個設計模式。核心原則是:將行爲從依賴解決中分離。在咱們的例子中,RealBillingService
沒有責任查找Transaction
和CreditCardProcessor
。相反,它們做爲構造函數參數傳入:
public class RealBillingService implements BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } } }
咱們不須要任何的工廠,並且咱們能夠經過去除setUp
和tearDown
樣板代碼來簡化咱們的測試用例:
public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100); private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog(); private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); public void testSuccessfulCharge() { RealBillingService billingService = new RealBillingService(processor, transactionLog); Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge()); assertEquals(100, receipt.getAmountOfCharge()); assertEquals(creditCard, processor.getCardOfOnlyCharge()); assertEquals(100, processor.getAmountOfOnlyCharge()); assertTrue(transactionLog.wasSuccessLogged()); } }
如今,任什麼時候候咱們增長或者移除了依賴關係,編譯器將會提示咱們那些測試須要被修改。依賴關係在API簽名中公開。
不幸的是,如今BillingService
的客戶端代碼須要查找它的依賴。咱們能夠經過在應用一次依賴注入模式來解決其中的一下問題。以來BillingService
的類能夠在它們的構造函數接受一個BillingService
。對於頂層的類來講,有一個框架是有用的。不然,當咱們須要使用一個服務時,咱們將須要遞歸地構造依賴。
依賴注入模式使得是代碼模塊化的和可測試的,Guice使使用依賴注入模式的代碼易於編寫。爲了在咱們的帳單例子中使用Guice
,咱們首先須要告訴它怎麼映射咱們的接口到它們的實現。這個配置在一個Guice
模塊中完成,Guice模塊是一個實現了Module
接口:
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class); bind(BillingService.class).to(RealBillingService.class); } }
咱們添加了@Inject
註解到RealBillingService
的構造函數,它指示Guice
來使用它。Guice
將檢查被註解的構造函數,爲每一個參數查找值。
public class RealBillingService implements BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; @Inject public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } } }
最後,咱們能夠將它們放到一塊兒。Inject
能夠被用來獲取任何被綁定類的一個實例。
public static void main(String[] args) { Injector injector = Guice.createInjector(new BillingModule()); BillingService billingService = injector.getInstance(BillingService.class); }
Getting started解釋了這是怎麼工做的。