第一章 Guice入门:Guice指导教程(原创翻译)

目录
[隐藏]

第一章 Guice入门 Motivation

把应用开发过程中的所有东西(代码)堆在一起,是件糟糕的事情。当然,我们可以用很多方法来组织数据,服务以及类之间的关系。为了对比说明这些方法,我们先写一个订购披萨(pizza)的网站处理程序:

Wiring everything together is a tedious part of application development. There are several approaches to connect data, service, and presentation classes to one another. To contrast these approaches, we’ll write the billing code for a pizza ordering website:

public interface BillingService {

  /**
   * Attempts to charge the order to the credit card. Both successful and
   * failed transactions will be recorded.
   *
   * @return a receipt of the transaction. If the charge was successful, the
   *      receipt will be successful. Otherwise, the receipt will contain a
   *      decline note describing why the charge failed.
   */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

这段程序是一个接口,拥有一个方法——记录订单chargeOrder

接下来我们将写一个该接口的具体实现,并测试我们的代码。这段测试代码中,我们需要一个名为“FakeCreditCardProcessorto”的类,这个类模拟信用卡的刷卡过程,可以理解为模拟的Pos机(这样我们就不需要用一个真的信用卡做测试了)。

Along with the implementation, we’ll write unit tests for our code. In the tests we need a FakeCreditCardProcessorto avoid charging a real credit card!



直接调用 Direct constructor calls

在下面的客户端代码中,创建(new)了一个信用卡Pos机支付实例,另外还包含了交易的日志记录:

Here’s what the code looks like when we just new up the credit card processor and transaction logger:

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

这段程序的模块化和可测试性都存在一些问题。例如这段程序中直接依赖了一个真实的Pos机(new PaypalCreditCardProcessor()),这就意味着当我们测试时,会真的从信用卡里扣钱!另外,如果Pos机扣费不成功或者机器本身出了问题,那么我们就无从下手了。

This code poses problems for modularity and testability. The direct, compile-time dependency on the real credit card processor means that testing the code will charge a credit card! It’s also awkward to test what happens when the charge is declined or when the service is
unavailable.

工厂方法 Factories

工厂方法能将客户端代码与具体的实现进行解耦。一个“简单工厂”可以用静态方法来获取或设置接口的具体实现,因此我们就可以模拟一个假的pos机。“工厂”都是采用如下类似结构实现:

A factory class decouples the client and implementing class. A simple factory uses static methods to get and set mock implementations for interfaces. A factory is implemented with some boilerplate code:

public class CreditCardProcessorFactory {
  
  private static CreditCardProcessor instance;
  
  public static void setInstance(CreditCardProcessor creditCardProcessor) {
    instance = creditCardProcessor;
  }

  public static CreditCardProcessor getInstance() {
    if (instance == null) {
      return new SquareCreditCardProcessor();
    }
    
    return instance;
  }
}

我们为Pos机“盖”了一个工厂(CreditCardProcessorFactory,这个“工厂”用来生产Pos机(new SquareCreditCardProcessor())。

我们用新的工厂方法重写刚才的客户端代码。

In our client code, we just replace the new calls with factory lookups:

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

如此一来,我们通过“工厂”,将客户端代码里的具体实现进行解耦了。

这样便可以进行单元测试了:

The factory makes it possible to write a proper unit test:

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

但是,这段代码仍然是糟糕的。我们可以看到,一个全局变量引用了一个具体的“模拟实现”,或者叫“假实现”。所以我们需要很明确的对他进行初始化以及销毁操作。但如果销毁失败了,这个全局变量就会一直指向我们的“模拟实例”。这会影响其他的测试,甚至阻止并行运行多个测试。

This code is clumsy. A global variable holds the mock implementation, so we need to be careful about setting it up and tearing it down. Should the tearDown fail, the global variable continues to point at our test instance. This could cause problems for other tests. It also prevents us from running multiple tests in parallel.


另外,这里最大的问题在于依赖关系被隐藏在其他的代码中了。如果我们在CreditCardFraudTracker中添加了一个依赖关系,我们就不得不重新执行测试过程,然后再观察哪些地方会出错。我们可能会忘了初始化一个工厂的服务,只有等到执行一个真正的信用卡扣费动作时,我们才会发现问题。随着应用规模增长,这种“小作坊”的生产力已经跟不上了。

But the biggest problem is that the dependencies are hidden in the code. If we add a dependency on a CreditCardFraudTracker, we have to re-run the tests to find out which ones will break. Should we forget to initialize a factory for a production service, we don’t find out until a charge is attempted. As the application grows, babysitting factories becomes a growing drain on productivity.

虽然质量问题将会被QA或者测试用例发现,但我们还是应该做的更好。

Quality problems will be caught by QA or acceptance tests. That may be sufficient, but we can certainly do better.

依赖注入 dependency injection

像工厂模式,依赖注入也是一种设计模式。核心是将行为动作从依赖解析中分离开。在我们的例子中,客户端RealBillingService并不负责查找或者创建TransactionLog和CreditCardProcessor对象。相反,这两个对象是通过构造函数的参数进行传入:

Like the factory, dependency injection is just a design pattern. The core principal is to separate behaviour from dependency resolution. In our example, the RealBillingService is not responsible for looking up the TransactionLog and CreditCardProcessor. Instead, they’re passed in as constructor parameters:

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方法简化了测试用例:

We don’t need any factories, and we can simplify the testcase by removing the setUp and tearDown boilerplate:

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接口(构造方法的参数)给暴露出来了。

Now, whenever we add or remove dependencies, the compiler will remind us what tests need to be fixed. The dependency is exposed in the API signature.

还是很不幸,现在BillingService的调用者需要负责查找依赖关系了。但我们可以通过相同的方式解决这个问题!依赖BillingService的类可以通过他们的构造方法传入到BillingService对象中。在最顶层的类中,如果能有一个框架将会有很大帮助。否则当需要使用相应服务的时候,你需要采用递归的方式来构建依赖关系。

Unfortunately, now the clients of BillingService need to lookup its dependencies. We can fix some of these by applying the pattern again! Classes that depend on it can accept a BillingService in their constructor. For top-level classes, it’s useful to have a framework. Otherwise you’ll need to construct dependencies recursively when you need to use a service:

  public static void main(String[] args) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();
    BillingService billingService
        = new RealBillingService(creditCardProcessor, transactionLog);
    ...
  }



用Guice实现依赖注入 Dependency Injection with Guice

依赖注入模式可以将代码模块化并易于测试,而且更容易书写。为了在我们的账单(billing)例子中使用Guice,我们首先要告诉Guice如何映射接口和实现。这样的映射关系是通过Guice的模块(module)进行配置实现的,这个模块(module)通过普通Java代码实现。

The dependency injection pattern leads to code that’s modular and
testable, and Guice makes it easy to write. To use Guice in our billing
example, we first need to tell it how to map our interfaces to their
implementations. This configuration is done in a Guice
module, which is any Java class that implements the Module interface:

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

我们可以在RealBillingService的构造函数上添加一个@Inject注解,这个注解通知Guice。Guice会检测到被标注的构造函数,然后为每个参数查找对应的内容。

We add @Inject to RealBillingService’s
constructor, which directs Guice to use it. Guice will inspect the
annotated constructor, and lookup values for each of parameter.

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

最后,使用注入器(Injector)获取相应的实例。

Finally, we can put it all together. The Injector can be used to get an instance of any of the bound classes.

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new BillingModule());
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }

下一章:Guice 快速上手


说明:

      鉴于网上guice中文资料较少,出于个人爱好,对该项目下的用户API文档进行翻译。如有翻译不恰当之处,还望指正。

      google Guice 项目地址:https://github.com/google/guice

      Guice 英文API地址:https://github.com/google/guice/wiki/Motivation

分享到:

发表评论

昵称

沙发空缺中,还不快抢~