我们模拟一个披萨收费的系统.首先是收费的接口定义.
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);
}
下面是直接的实现:
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());
}
}
}
上面的代码中processor和transactionLog的初始化话耦合到实现里面去了,所以导致无法测试chargeOrder方法,因为你没有一个真实的PaypalCreditCardProcessor, 当processor和transactionLog的实现变更时也无法维护.
首先我们想到的是使用工厂方法来重构上面的代码.
很容易想到用工厂方法来解决上面的问题,就是为processor和transactionLog提供工厂类
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;
}
}
有了工厂方法,那么我们的实现就可以改成下面这样.
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());
}
}
但是工厂方法的实现,太复杂了而且也不易于维护,首先你要初始化(setUp),用完过后还有清理(tearDown).如果tearDown失败的话,那么初始化 的全局变量则会一致存在程序中.而且也不能多线程运行.
但最重要的是,依赖关系被隐藏在代码中去了.而且随着应用越来越复杂,那么子工厂也越来越多.
与工厂模式一样,依赖注入也是一种设计模式.下面我们使用构造函数的依赖注入的方式.
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());
}
}
现在,无论添加还是删除依赖,编译器都会提醒我们什么需要修改.依赖会被暴露给接口.
但是,这样也有一个确定,就是客户端需要查找依赖.如下面的代码中,需要客户端去初始化实例.
public static void main(String[] args) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog();
BillingService billingService
= new RealBillingService(processor, transactionLog);
...
}
依赖注入使得代码模块化,可测试.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,Guice就会根据module来查找接口对应绑定的实现,自动注入.
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());
}
}
}
客户端实现,就非常简单了.此处客户端就完全不用管具体实现了,而且具体实现也不会嵌入到代码中,而是存在我们自己定义module中.
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
...
}