Guice中文文档

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

Motivation

将所有东西整合到一起是开发中一项乏味的工作,现在有多种方法将不同的数据、服务和展现层互相联系起来。为了对比这些不同的方法, 我们为一个披萨在线订购网站编写了计费代码

public interface BillingService {

  /**
   *  尝试通过信用卡支付,无论是否成功都将被记录下来
   * @return 支付成功时返回成功信息,否则,返回失败原因
   *        
 */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

在实现这个接口之前,我们要写一个单元测试。在测试中我们需要一个FakeCreditCardProcessor,因为我们不能真的从一张信用卡中刷钱=。=

Direct constructor calls

下面展示了如果我们只new一个信用卡processor和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());
    }
  }
}

如上代码耦合性很高,并且不易测试。单元测试里不可能对一张真实的信用卡进行操作。

Factories

一个工厂类解耦了客户端和它的实现类。一个简单的工厂使用静态方法来get和set一个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;
  }
}

在我们的客户端代码中,我们只是在原先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());
    }
  }
}

有了工厂,我们就可以实现一个正确的UT

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

这看起来很笨拙。一个全局变量持有mock实现,所以在设置和销毁时我们需要很小心。如果tearDown失败了,这个全局变量将会继续指向我们的测试实例,这将会对其他的UT产生影响,我们也不能并行地运行多个测试用例。
但是,最大的问题在于,依赖被隐藏在代码里。如果我们要添加一个新的依赖CreditCardFraudTracker,我们必须重新运行UT来找到which ones will break(没看懂)

Dependency Injection

跟工厂一样,DI也只是一个设计模式,其核心原则是将行为和解析依赖分离开(separate behaviour from dependency resolution)。在我们的栗子里,RealBillingService不负责寻找TransactionLog和CreditCardProcessor,相反,它们作为构造函数的参数被传递给BillingService

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给依赖它的类作为构造器的参数。简而言之,提供一个框架给顶端的类总不是个坏事。

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

Dependency Injection with Guice

下面就要介绍到我们的Guice了,DI模式使得代码更加易于测试和可维护,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会检查被注解的构造函数,然后找到每个参数的值。

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

最后,我们可以把它们放在一起了。

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

下一章解释了这一切是怎么工作的。

你可能感兴趣的:(Guice中文文档)