原文链接

动机

将所有的内容连接在一起时应用开发的一个单调乏味的部分。有几种方式来将数据、服务、presetntation类连接到一起。为了对比这些方法,我将为披萨订购网站编写账单代码:

public interface BillingService {
// 尝试在信用卡中扣除订单的费用。成功和失败的交易都会被记录
Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

伴随着实现,我们将为我们的代码编写单元测试。在测试中,我们需要一个FakeCreditCardProcessor来避免从真实的信用卡扣费!

直接构造函数调用

以下是,当我们只是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());
}
}
}

该代码给模块化和可测试性带来问题。对真实信用卡处理器的直接编译时依赖意味着测试代码将从信用卡中扣费。当发生扣费被拒绝或者当服务不可用的事情时,对测试是很不方便的。

工厂

工厂类可以解耦客户端代码和实现类。一个简单工厂使用静态方法来获取和设置接口的模式实现。一个工厂使用一些样板代码实现:

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没有责任查找TransactionCreditCardProcessor。相反,它们作为构造函数参数传入:

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());
}
}
}

我们不需要任何的工厂,而且我们可以通过去除setUptearDown样板代码来简化我们的测试用例:

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模块中完成,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解释了这是怎么工作的。

【翻译】 Guice 动机——依赖注入的动机的更多相关文章

  1. 依赖注入与Unity(一) 介绍

        在你学习依赖注入和Unity之前,你需要明白你为什么要使用它们.为了明白为什么要使用它们,你应该明白依赖注入和Unity能够帮助你解决什么类型的问题.作为介绍部分,这一章不会涉及太多关于Uni ...

  2. ABP(现代ASP.NET样板开发框架)系列之6、ABP依赖注入

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  3. ABP依赖注入

    ABP依赖注入 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.N ...

  4. Spring之 IOC&依赖注入

    0x01.Spring 1什么是Spring ​ Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的(解耦). ​ 框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组 ...

  5. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  6. [Android]使用Dagger 2进行依赖注入 - Producers(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...

  7. [Android]使用Dagger 2依赖注入 - API(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...

  8. [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...

  9. [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...

随机推荐

  1. Django入门之路

    Web框架开发-Django基础之web应用,Http协议 web框架开发-web框架简介,wsgiref模块,DIY一个web框架 web框架开发-Django简介 web框架开发-静态文件配置 w ...

  2. 移动端左右滑动问题-html与css解决

    <!DOCTYPE html> <html> <head> <title>纯css实现左右滑动</title> <style type ...

  3. 毕业设计5:基于MicroPython的智能火灾报警器系统的设计与实现

    随着现代家庭用火.用电量的增加,家庭火灾发生的频率越来越高.家里一旦发生火灾,如果出现扑救不及时.灭火器材缺乏.以及在场人惊慌失措.逃生迟缓等不利情况下,最终就会导致产生重大的生命财产的损失. 消防部 ...

  4. Xcode: Run Script 的运用, 使build打包后自动+1

    背景: 每次打包都要build+1处理,比较麻烦,使用 Run Script 的运用使build打包后自动+1 0. 使用xcode 添加run Script 然后就可以添加Run Script了 1 ...

  5. Swagger 报错 no mapping found for http request with uri [/***/swagger-ui.html] in dispatcherservlet with name '***'

    swagger报错: no mapping found for http request with uri [/***/swagger-ui.html] in dispatcherservlet wi ...

  6. 【C语言】位运算

    编写一个函数getbits,从一个16位的单元中取出某几位(即该几位保留原值,其余位0).函数调用形式为getbits(value,n1,2).----简单题目遇到想不到的问题 c语言位运算经典问题: ...

  7. 写一个Python 1、通过select实现的最简单的web框架2、通过wsgiref实现的web框架

    #!/usr/bin/env python # -*- coding: utf- -*- import socket import select class MyRequest: "&quo ...

  8. JS继承以及继承的几种实现方式总结

    传统面向对象语言:继承是类与类之间的关系. 而在js中由于es6之前没有类的概念,所以继承是对象与对象之间的关系. 在js中,继承就是指使一个对象有权去访问另一个对象的能力. 比如:比如对象a能够访问 ...

  9. C#入门教程源码

    C#入门教程源码 [日期:2019-01-26] 来源:51zxw.net  作者:zhangguofu [字体:大 中 小] 方法一:百度云盘下载地址: 链接:https://pan.baidu.c ...

  10. 【NLP】选择目标序列:贪心搜索和Beam search

    构建seq2seq模型,并训练完成后,我们只要将源句子输入进训练好的模型,执行一次前向传播就能得到目标句子,但是值得注意的是: seq2seq模型的decoder部分实际上相当于一个语言模型,相比于R ...