Google Guice 用户指南 - Ⅱ:愿景

打印 上一主题 下一主题

主题 892|帖子 892|积分 2686

译者:kefate
原文:https://github.com/google/guice/wiki/Motivation
将所有组件连接在一起是应用程序开发中繁琐的一部分。有多种方法可以将数据、服务和表示层类连接在一起。为了对比这些方法,我们将编写一个披萨订购网站的计费代码:
  1. public interface BillingService {
  2.   /**
  3.    * 尝试使用信用卡对订单进行扣款。成功和失败的交易都将被记录
  4.    *
  5.    * @return 交易的收据。如果扣款成功,收据将显示成功信息;否则,收据将包含一份拒绝说明,描述为何扣款失败。
  6.    */
  7.   Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
  8. }
复制代码
除了实现代码之外,我们还将为我们的代码编写单元测试。在测试中,我们需要一个FakeCreditCardProcessor来避免对真实信用卡进行扣款。
直接调用构造方法

当我们仅仅使用new来创建CreditCardProcessor 和 TransactionLog对象时,代码如下:
  1. public class RealBillingService implements BillingService {
  2.   public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  3.     CreditCardProcessor processor = new PaypalCreditCardProcessor();
  4.     TransactionLog transactionLog = new DatabaseTransactionLog();
  5.     try {
  6.       ChargeResult result = processor.charge(creditCard, order.getAmount());
  7.       transactionLog.logChargeResult(result);
  8.       return result.wasSuccessful()
  9.           ? Receipt.forSuccessfulCharge(order.getAmount())
  10.           : Receipt.forDeclinedCharge(result.getDeclineMessage());
  11.      } catch (UnreachableException e) {
  12.       transactionLog.logConnectException(e);
  13.       return Receipt.forSystemFailure(e.getMessage());
  14.     }
  15.   }
  16. }
复制代码
这段代码带来了模块化和可测试性的问题。这种对真实的信用卡处理器PaypalCreditCardProcessor 的编译时的直接依赖,意味着测试这段代码时将会用真实的信用卡进行扣款!并且,当向PaypalCreditCardProcessor 扣款被拒绝或者其服务不可用时,测试代码也会变得困难。
工厂类

使用工厂类可以将客户端和实现类进行解耦。下面是一个简单的工厂类,它用static方法来获取实例对象/设置 mock 对象:
  1. public class CreditCardProcessorFactory {
  2.   private static CreditCardProcessor instance;
  3.   public static void setInstance(CreditCardProcessor processor) {
  4.     instance = processor;
  5.   }
  6.   public static CreditCardProcessor getInstance() {
  7.     if (instance == null) {
  8.       return new SquareCreditCardProcessor();
  9.     }
  10.     return instance;
  11.   }
  12. }
复制代码
在我们的客户端代码中,我们只需要用工厂类的getInstance方法替换掉原来通过new创建对象的逻辑即可:
  1. public class RealBillingService implements BillingService {
  2.   public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  3.     CreditCardProcessor processor = CreditCardProcessorFactory.getInstance();
  4.     TransactionLog transactionLog = TransactionLogFactory.getInstance();
  5.     try {
  6.       ChargeResult result = processor.charge(creditCard, order.getAmount());
  7.       transactionLog.logChargeResult(result);
  8.       return result.wasSuccessful()
  9.           ? Receipt.forSuccessfulCharge(order.getAmount())
  10.           : Receipt.forDeclinedCharge(result.getDeclineMessage());
  11.      } catch (UnreachableException e) {
  12.       transactionLog.logConnectException(e);
  13.       return Receipt.forSystemFailure(e.getMessage());
  14.     }
  15.   }
  16. }
复制代码
这种工厂类的写法让我们有能力写出一个恰当的单元测试出来:
  1. public class RealBillingServiceTest extends TestCase {
  2.   private final PizzaOrder order = new PizzaOrder(100);
  3.   private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
  4.   private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  5.   private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
  6.   @Override public void setUp() {
  7.     TransactionLogFactory.setInstance(transactionLog);
  8.     CreditCardProcessorFactory.setInstance(processor);
  9.   }
  10.   @Override public void tearDown() {
  11.     TransactionLogFactory.setInstance(null);
  12.     CreditCardProcessorFactory.setInstance(null);
  13.   }
  14.   public void testSuccessfulCharge() {
  15.     RealBillingService billingService = new RealBillingService();
  16.     Receipt receipt = billingService.chargeOrder(order, creditCard);
  17.     assertTrue(receipt.hasSuccessfulCharge());
  18.     assertEquals(100, receipt.getAmountOfCharge());
  19.     assertEquals(creditCard, processor.getCardOfOnlyCharge());
  20.     assertEquals(100, processor.getAmountOfOnlyCharge());
  21.     assertTrue(transactionLog.wasSuccessLogged());
  22.   }
  23. }
复制代码
这种代码写起来就比较蠢。全局变量instance在这里被设置成了一个 mock 对象,所以我们需要很小心去设置它的值或者把它恢复为初始值null。如果tearDown方法执行失败了,这个全局变量就会一直是我们创建的 mock 对象。这样可能会给其他的测试带来问题,并且我们也无法并发运行多个测试。
但最大的问题其实是依赖关系被隐藏到了代码里。如果工厂类中增加了对CreditCardFraudTracker的依赖,我们必须要重新进行测试才能找出哪些测试会被破坏。如果测试类中忘记初始化一个工厂,那就只能在最后调用的时候才发现。随着应用程序规模的增长,时刻关注工厂类相关的代码就成了一件很折磨人的事情。
虽然QA会保证软件的质量,但我们身为研发,还是要尽可能地做得更好一些。
依赖注入

就像工厂模式一样,依赖注入其实也只是一种设计模式。其核心原则是将行为与依赖进行解耦。在我们的示例中,RealBillingService 不负责查找 TransactionLog 和 CreditCardProcessor对象,而是把他们作为构造函数参数传递进来:
  1. public class RealBillingService implements BillingService {
  2.   private final CreditCardProcessor processor;
  3.   private final TransactionLog transactionLog;
  4.   public RealBillingService(CreditCardProcessor processor,
  5.       TransactionLog transactionLog) {
  6.     this.processor = processor;
  7.     this.transactionLog = transactionLog;
  8.   }
  9.   public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  10.     try {
  11.       ChargeResult result = processor.charge(creditCard, order.getAmount());
  12.       transactionLog.logChargeResult(result);
  13.       return result.wasSuccessful()
  14.           ? Receipt.forSuccessfulCharge(order.getAmount())
  15.           : Receipt.forDeclinedCharge(result.getDeclineMessage());
  16.      } catch (UnreachableException e) {
  17.       transactionLog.logConnectException(e);
  18.       return Receipt.forSystemFailure(e.getMessage());
  19.     }
  20.   }
  21. }
复制代码
既然我们现在不需要任何工厂类,那么通过删除单测的 setUp 和 tearDown 方法,我们就可以简化单测:
  1. public class RealBillingServiceTest extends TestCase {
  2.   private final PizzaOrder order = new PizzaOrder(100);
  3.   private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
  4.   private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  5.   private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
  6.   public void testSuccessfulCharge() {
  7.     RealBillingService billingService
  8.         = new RealBillingService(processor, transactionLog);
  9.     Receipt receipt = billingService.chargeOrder(order, creditCard);
  10.     assertTrue(receipt.hasSuccessfulCharge());
  11.     assertEquals(100, receipt.getAmountOfCharge());
  12.     assertEquals(creditCard, processor.getCardOfOnlyCharge());
  13.     assertEquals(100, processor.getAmountOfOnlyCharge());
  14.     assertTrue(transactionLog.wasSuccessLogged());
  15.   }
  16. }
复制代码
现在,每当我们添加或删除依赖关系时,编译器会提醒我们哪些测试需要修正。依赖关系暴露到了API签名里
不幸的是,现在用到BillingService的客户端需要查找它的依赖关系。我们可以通过再次应用该模式来解决一些问题——依赖于它的类可以在它们的构造函数中接受一个BillingService。不难发现,对于顶层的那些类来说,最好能有一个框架来完成依赖注入这件事。否则,当你需要使用一个服务时,你就需要递归地构建依赖关系。
  1.   public static void main(String[] args) {
  2.     CreditCardProcessor processor = new PaypalCreditCardProcessor();
  3.     TransactionLog transactionLog = new DatabaseTransactionLog();
  4.     BillingService billingService
  5.         = new RealBillingService(processor, transactionLog);
  6.     ...
  7.   }
复制代码
使用 Guice 进行依赖注入

使用依赖性注入这种设计模式提高了代码的模块化和可测试性,而 Guice 框架使其易于编写。
为了在我们的计费例子中使用 Guice,首先,我们需要告诉它如何将接口与实现类进行映射。通过一个实现了Module接口的 Java 类,就能完成这项配置。
  1. public class BillingModule extends AbstractModule {
  2.   @Override
  3.   protected void configure() {
  4.     bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  5.     bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
  6.     bind(BillingService.class).to(RealBillingService.class);
  7.   }
  8. }
复制代码
接下来,我们只要在RealBillingService的构造函数中加入@Inject就可以告诉 Guice 进行处理。Guice将检查使用了@Inject注解的构造函数,并为每个参数查找实际的值。
  1. public class RealBillingService implements BillingService {
  2.   private final CreditCardProcessor processor;
  3.   private final TransactionLog transactionLog;
  4.   @Inject
  5.   public RealBillingService(CreditCardProcessor processor,
  6.       TransactionLog transactionLog) {
  7.     this.processor = processor;
  8.     this.transactionLog = transactionLog;
  9.   }
  10.   public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  11.     try {
  12.       ChargeResult result = processor.charge(creditCard, order.getAmount());
  13.       transactionLog.logChargeResult(result);
  14.       return result.wasSuccessful()
  15.           ? Receipt.forSuccessfulCharge(order.getAmount())
  16.           : Receipt.forDeclinedCharge(result.getDeclineMessage());
  17.      } catch (UnreachableException e) {
  18.       transactionLog.logConnectException(e);
  19.       return Receipt.forSystemFailure(e.getMessage());
  20.     }
  21.   }
  22. }
复制代码
最后,我们把这些打包到一起,使用Injector 即可获得任何一个映射的类实例。
  1.   public static void main(String[] args) {
  2.     Injector injector = Guice.createInjector(new BillingModule());
  3.     BillingService billingService = injector.getInstance(BillingService.class);
  4.     ...
  5.   }
复制代码
下一节我们将会介绍这一切是如何运作的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表