在应用中组装各个封装好的类,有时候是一件很乏味的事情。有几种办法可以把数据层、业务层、表现层的代码整合在一起。下面通过一个在线披萨下订单的业务来对比这几种实现方法。
// 定义下订单接口
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);
}
复制代码
  
我们需要为下订单实现类写单元测试,这里为了避免真正的支付,我们需要定义一个    FakeCreditCardProcessor
类  
下面示例代码中,直接在构造方法中    new
信用卡处理类    CreditCardProcessor
跟事务日志处理类    TransactionLog
  
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());
    }
  }
}
复制代码
  上面代码的主要问题是没有进行模块化,也不好测试。不然,你测试的时候,真的要从你的信用卡扣费了。而且,也很难测试支付失败,或者支付网关服务不可用的情况。
工厂方法解耦了调用类跟实现类之间的耦合。一般工厂方法使用静态的set跟get方法来设置跟获取实现类。如下面的示例代码:
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());
  }
}
复制代码
  
上面代码看起来也很笨拙。因为使用全局变量来保存模拟支付类    FakeCreditCardProcessor
的实例,需要在    teardown
对于全局变量进行释放。如果    teardown
执行失败,而且后面的测试也用到了这个变量,会对后面的测试造成影响。同样,由于全局变量的污染,也无法进行并行测试。  
最严重的问题是,所有的依赖都隐藏码在代码中了。比如,在    CreditCardFraudTracker
类中增加了一个依赖,所有的单元测试都要跑一遍,来看一下哪个测试方法没有通过。
我们也很难知道一个工厂方法是否初始化,除非哪天被调用到了。  
虽然QA跟充分的验收测试能解决这些问题,但是我们肯定有更好的办法来处理这个问题。
跟工厂模式一样,依赖注入也是一种设计模式。依赖注入的核心原则是:把使用依赖跟查找依赖分离。就像下面的例子,    RealBillingService
并不负责查找    TransactionLog
和    CreditCardProcessor
,而是由使用者传入到对应的构造函数中。  
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());
  }
}
复制代码
  
如果我们在    RealBillingService
增加依赖,编译器会提醒我们哪个测试方法需要被修复了。  
但是现在    BillingService
的使用者需要知道它的依赖,并在构造方法中传入这些依赖,通常在一个入口类传入。  
public static void main(String[] args) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();
    BillingService billingService
        = new RealBillingService(processor, transactionLog);
    ...
  }
复制代码
  依赖注入让设计模式让代码可以模块化、可测试化,Guice让依赖注入的代码更容易书写。
在上面的例子中使用Guice的话,我们首先要告诉Guice怎么通过接口找到它的实现类。我们通过一个实现了Guice    Module
接口的配置类来完成这个工作。  
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);
  }
}
复制代码
  
我们通过添加    @Inject
到    RealBillingService
的构造方法,让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());
    }
  }
}
复制代码
  
最后,可以使用    Injector
去帮我们找到任何一个绑定过的类的实例。  
public static void main(String[] args) {
    Injector injector = Guice.createInjector(new BillingModule());
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }
复制代码
  原文链接: www.wetimer.com/wei-shi-yao…