基于条件的依赖注入

时间:2011-08-06 17:34:24

标签: java dependency-injection guice

我正在使用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);

最好的方法是什么?我是依赖注入的新手。

4 个答案:

答案 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中包含的多个绑定。然后,这是一个实施的问题。

您需要一个密钥,在您的情况下可以是StringEnumeration并将所有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来控制生命周期,甚至可以实现自己的提供程序来完成其他任务。