Google Guice 用户指南 - Ⅱ:愿景

这篇具有很好参考价值的文章主要介绍了Google Guice 用户指南 - Ⅱ:愿景。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

译者:kefate

原文:https://github.com/google/guice/wiki/Motivation

将所有组件连接在一起是应用程序开发中繁琐的一部分。有多种方法可以将数据、服务和表示层类连接在一起。为了对比这些方法,我们将编写一个披萨订购网站的计费代码:

public interface BillingService {

  /**
   * 尝试使用信用卡对订单进行扣款。成功和失败的交易都将被记录
   *
   * @return 交易的收据。如果扣款成功,收据将显示成功信息;否则,收据将包含一份拒绝说明,描述为何扣款失败。
   */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

除了实现代码之外,我们还将为我们的代码编写单元测试。在测试中,我们需要一个FakeCreditCardProcessor来避免对真实信用卡进行扣款。

直接调用构造方法

当我们仅仅使用new来创建CreditCardProcessorTransactionLog对象时,代码如下:

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

这段代码带来了模块化和可测试性的问题。这种对真实的信用卡处理器PaypalCreditCardProcessor 的编译时的直接依赖,意味着测试这段代码时将会用真实的信用卡进行扣款!并且,当向PaypalCreditCardProcessor 扣款被拒绝或者其服务不可用时,测试代码也会变得困难。

工厂类

使用工厂类可以将客户端和实现类进行解耦。下面是一个简单的工厂类,它用static方法来获取实例对象/设置 mock 对象:

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

在我们的客户端代码中,我们只需要用工厂类的getInstance方法替换掉原来通过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());
  }
}

这种代码写起来就比较蠢。全局变量instance在这里被设置成了一个 mock 对象,所以我们需要很小心去设置它的值或者把它恢复为初始值null。如果tearDown方法执行失败了,这个全局变量就会一直是我们创建的 mock 对象。这样可能会给其他的测试带来问题,并且我们也无法并发运行多个测试。

但最大的问题其实是依赖关系被隐藏到了代码里。如果工厂类中增加了对CreditCardFraudTracker的依赖,我们必须要重新进行测试才能找出哪些测试会被破坏。如果测试类中忘记初始化一个工厂,那就只能在最后调用的时候才发现。随着应用程序规模的增长,时刻关注工厂类相关的代码就成了一件很折磨人的事情。

虽然QA会保证软件的质量,但我们身为研发,还是要尽可能地做得更好一些。

依赖注入

就像工厂模式一样,依赖注入其实也只是一种设计模式。其核心原则是将行为与依赖进行解耦。在我们的示例中,RealBillingService 不负责查找 TransactionLogCreditCardProcessor对象,而是把他们作为构造函数参数传递进来:

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。不难发现,对于顶层的那些类来说,最好能有一个框架来完成依赖注入这件事。否则,当你需要使用一个服务时,你就需要递归地构建依赖关系。

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

使用 Guice 进行依赖注入

使用依赖性注入这种设计模式提高了代码的模块化和可测试性,而 Guice 框架使其易于编写。

为了在我们的计费例子中使用 Guice,首先,我们需要告诉它如何将接口与实现类进行映射。通过一个实现了Module接口的 Java 类,就能完成这项配置。

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将检查使用了@Inject注解的构造函数,并为每个参数查找实际的值。

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 即可获得任何一个映射的类实例。

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

下一节我们将会介绍这一切是如何运作的。文章来源地址https://www.toymoban.com/news/detail-746268.html

到了这里,关于Google Guice 用户指南 - Ⅱ:愿景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 小程序安全指南:保护用户数据的最佳实践

      第一章:引言 近年来,小程序已成为移动应用开发的重要组成部分。它们为用户提供了方便的功能和个性化的体验,然而,与此同时,小程序安全问题也引起了广泛的关注。保护用户数据是开发者应该高度重视的问题。在本指南中,我们将介绍保护小程序用户数据的最佳实

    2024年02月15日
    浏览(50)
  • 全屋智能家居搭建初级指南(装修用户)

    小M等智能设备 新装修用户 稳定网络环境 规划好电路布局 全屋智能家居如何搭建,初级指南(装修用户) 一、装修中需要注意什么? —句话概括,需在水电进场前考虑智能家居设计,主要准备两件事:铺设网络和留好电路 1.铺设网络 智能设备越多,越需要—张稳定的网络

    2024年02月10日
    浏览(41)
  • 网站优化指南:提升用户体验与搜索引擎排名

    💂 个人网站:【海拥】【游戏大全】【神级源码资源网】 🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】 💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 拥有一个优化的网站对于吸引用户、提升用户体验以及在搜索引擎中获得更好的排名至

    2024年02月12日
    浏览(136)
  • Mac 用户必看:Postman 下载与安装指南

    Postman  是一个非常强大的 API 测试工具,它可以让你轻松地发送各种 HTTP 请求,查看响应结果,管理测试用例和环境变量等等。如果你想成为一个优秀的 API 开发者或者测试者,那么 Postman 是你必不可少的利器。 那么,如何在 Mac 电脑上下载和安装 Postman 呢?其实很简单,只

    2024年02月12日
    浏览(46)
  • Qualcomm 机器人 RB5 开发套件用户指南(4)

    4.5.3 相机用例 本节介绍以下相机用例:本地显示实时预览、设备中保存的视频录制、显示快照、视频录制快照、双机并发、多机多客户端。 本地显示相机实时预览 笔记 该export WAYLAND_DISPLAY=wayland-1命令仅适用于Linux嵌入式系统。 在 shell 窗口中运行以下测试命令adb: 下图为主

    2024年02月20日
    浏览(42)
  • CentOS 7 权限管理实战指南:用户管理相关命令详解

    掌握 CentOS 7 用户管理命令,轻松管理系统用户!本文详细介绍了在 CentOS 7 系统中常用的用户管理命令,从创建和删除用户、修改用户属性,到密码管理和用户权限设置,一应俱全。无论你是 Linux 新手还是经验丰富的管理员,这篇文章都将为你提供清晰而实用的用户管理技巧

    2024年01月17日
    浏览(51)
  • 金仓数据库KingbaseES安全指南--3.1. 用户管理

    目录 3.1.1. 关于用户安全 3.1.2. 预定义管理用户 3.1.3. 创建用户 3.1.4. 修改用户 3.1.5. 删除用户 3.1.6. 用户资源限制 3.1.7. 用户的数据字典视图 3.1.1. 关于用户安全 您可以通过设置口令和指定特殊限制来保护用户帐户。 KingbaseES 数据库初始化完成后,会创建三个用户:数据库

    2024年02月06日
    浏览(67)
  • Python 用户输入和字符串格式化指南

    Python 允许用户输入数据。这意味着我们可以向用户询问输入。在 Python 3.6 中,使用 input() 方法来获取用户输入。在 Python 2.7 中,使用 raw_input() 方法来获取用户输入。以下示例要求用户输入用户名,并在输入用户名后将其打印在屏幕上: Python 3.6: Python 2.7: 为了确保字符串按预

    2024年02月05日
    浏览(83)
  • HarmonyOS鸿蒙开发指南:构建用户界面 构建布局

    目录 布局说明 添加标题行和文本区域 添加图片区域 添加留言区域 添加容器

    2024年02月22日
    浏览(55)
  • Unity 欧盟UMP用户隐私协议Android接入指南

    Google 欧盟地区用户意见征求政策 UMPSDK接入文档 mainTemplate.gradle 中引入 项目路径下 Assets/Plugins/Android/mainTemplate.gradle CustomUnityPlayerActivity 导入UMP相关的包 java类中新增字段 初始化UMPSDK方法 在下面代码注释 “加载广告”的地方处理你的开屏广告 调用 测试 TEST-DEVICE-HASHED-ID 为你的

    2024年02月03日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包