我正在使用Google Guice进行依赖注入。假设我有以下内容:
public interface Payment {
public void pay();
}
public class PaymentCardImpl implements Payment {
public void pay() {
System.out.println("I pay with a card");
}
}
public class PaymentCashImpl implements Payment {
public void pay() {
System.out.println("I pay cash");
}
}
public class Order {
private Payment payment;
@Inject
public Order(Payment payment){
this.payment=payment;
}
public void finishOrder(){
this.payment.pay();
}
}
接下来,这是一个非常简单的绑定模块,如下所示:
public class MyModule extends AbstractModule {
@Override
protected void configure() {
bind(Payment.class).to(PaymentCashImpl.class);
}
}
如您所见,Payment
实例被注入Order构造函数。这是在MyModule
课程中完成的,总体而言非常酷。
我的主要看起来像:
public static void main(String[] args) {
MyModule module = new MyModule();
Injector injector = Guice.createInjector(module);
Order order = injector.getInstance(Order.class);
order.finishOrder();
}
然而,我无法看到,我是如何通过某种方式有条件地绑定PaymentCardImpl
或 a PaymentCashImpl
实例到Order
构造函数。
例如,假设订单是“在线”订单。我需要这个:
bind(Payment.class).to(PaymentCardImpl.class);
最好的方法是什么?我是依赖注入的新手。
答案 0 :(得分:11)
依赖注入对于创建service
样式对象很有用。它们具有以下特征: -
基于此,Payment
是服务对象。我会将其重命名为PaymentService
,以区别于您可能存储的有关付款的分类帐条目(这将是一个价值对象)。
您的示例并未显示Order
类的内容,但我认为它会包含某些订单项,投放地址和总金额等信息。这是一个value
对象。它代表了业务领域的一件事。
值对象在状态上很重,在行为上更轻。可以实现多种实现,但您不太可能希望将一种实现替换为另一种实现。
您的依赖项注入框架不会创建值对象。它们由您的业务逻辑代码创建。在您的示例中,您使用Guice创建所有对象。我希望实际上你需要根据用户输入在运行时创建Order
。
服务对象可以依赖于值对象,但从不相反。我认为你应该寻求实施:
checkoutService.payfor( order, method );
而不是order.finishOrder( method )
在CheckoutService
课程中,您可以选择合适的PaymentService
并将order
传递给它。 CheckoutService
将作为构造函数参数PaymentCardPaymentService
(相当于您的PaymentCardImpl)和CashPaymentService
(相当于您的PaymentCashImpl)。
答案 1 :(得分:9)
我知道你为什么要这样做。但我不会将构造代码(依赖注入配置)与业务逻辑混淆。如果这样做,您的业务逻辑可能不再可理解。对我来说,似乎你的条件注入取决于情况,即来自用户界面的输入。
那么为什么不注入两者并使条件明确?我更喜欢那个。一个示例应用程序:
public class MyModule extends AbstractModule {
@Override
protected void configure() {
}
public static void main(String[] args) {
MyModule module = new MyModule();
Injector injector = Guice.createInjector(module);
Order order = injector.getInstance(Order.class);
order.finishOrder(PaymentMethod.CARD);
}
}
public class PaymentProvider {
private final Payment cashPayment, cardPayment;
@Inject
public PaymentProvider(CardPayment cardPayment, CashPayment cashPayment) {
this.cardPayment = cardPayment;
this.cashPayment = cashPayment;
}
public Payment getPaymentByMethod(PaymentMethod method) {
switch (method) {
case CARD:
return cardPayment;
case CASH:
return cashPayment;
default:
throw new IllegalArgumentException("Unkown payment method: " + method);
}
}
}
public enum PaymentMethod { CASH, CARD }
public class Order {
private final PaymentProvider paymentProvider;
@Inject
public Order(PaymentProvider paymentProvider) {
this.paymentProvider = paymentProvider;
}
public void finishOrder(PaymentMethod method) {
paymentProvider.getPaymentByMethod(method).pay();
}
}
仍为您自己的实践:付款的东西。你不需要任何Guice代码。剩下的工作由Guice自动完成。如果您开始使用接口,您将开始使用绑定,如下所述:http://code.google.com/p/google-guice/wiki/GettingStarted。但是,如果您没有任何接口,则无需任何配置即可完成构建。 @Inject注释就足够了。
当然这只是一个示例设计。但它显示了使用Guice设置一个很好的解耦Java应用程序是多么容易。
答案 2 :(得分:5)
您可以注释要注入的是哪一个。如果您执行命名绑定,它将解决问题。
见下文:
bind(Payment.class).annotatedWith(Names.named("Card")).to(PaymentCardImpl.class);
bind(Payment.class).annotatedWith(Names.named("Cash")).to(PaymentCashImpl.class);
然后你要注射的地方:
@Named("Cash") Payment payment
或:
@Named("Card") Payment payment
答案 3 :(得分:4)
使用MapBinder扩展名,您可以注入Map
中包含的多个绑定。然后,这是一个实施的问题。
您需要一个密钥,在您的情况下可以是String
或Enumeration
并将所有Payment
实施绑定到相应的密钥:
public class MyModule extends AbstractModule {
@Override
protected void configure() {
// this one aggregates all bindings and could be further annotated if you
// need several maps with same key/value types
MapBinder<String, Payment> mapBinder = MapBinder.newMapBinder(binder(),
String.class, Payment.class);
// simple binding of PaymentCashImpl to 'cash' key
mapBinder.addBinding("cash").to(PaymentCashImpl.class);
// you can scope during binding or using @Singleton annotation on implementations
mapBinder.addBinding("card").to(PaymentCardImpl.class).in(Singleton.class);
}
}
然后在Order
中注入整个地图并决定使用哪个实现:
public class Order {
@Inject
Map<String, Provider<Payment>> paymentProcessors;
public void finishOrder(String paymentMethod) {
if (!paymentProcessors.containsKey(paymentMethod)) {
throw new IllegalArgumentException("Unknown payment method " + paymentMethod);
}
Payment paymentProcessor = paymentProcessors.get(paymentMethod).get();
// do your stuff...
paymentProcessor.pay();
}
}
注意:注入提供商不适用于javax.inject.Provider
,您需要使用com.google.inject.Provider
。
通过注入提供程序而不是实例,您可以实现延迟实例化和每个实现的不同实例化策略。与上面的示例一样,PaymentCardImpl
是单例,而另一个是每次创建的。您可以使用guice scopes来控制生命周期,甚至可以实现自己的提供程序来完成其他任务。