美文网首页
Guice中文文档

Guice中文文档

作者: 2fc2a81494ac | 来源:发表于2017-06-21 10:44 被阅读0次

    原文链接: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中文文档

          本文链接:https://www.haomeiwen.com/subject/epigqxtx.html