原文连接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解释了这是怎么工做的。