基于条件的依赖注入

Joe*_*dev 9 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,这是一个非常简单的绑定模块,如下所示:

public class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Payment.class).to(PaymentCashImpl.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,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();
}
Run Code Online (Sandbox Code Playgroud)

然而,我无法看到的是如何通过某种方式将一个PaymentCardImpl 一个PaymentCashImpl实例有条件地绑定到Order构造函数.

比方说,例如,订单是"在线"订单.我需要这个:

bind(Payment.class).to(PaymentCardImpl.class);
Run Code Online (Sandbox Code Playgroud)

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

WW.*_*WW. 11

依赖注入对于创建service样式对象很有用.它们具有以下特点: -

  • 可能有多种实现方式,
  • 沉重的行为,
  • 内部状态仅限于它们的依赖关系,它们通常不可变
  • 将映射到现实世界中的演员(例如收银员),而不是事物

基于此,Payment是一个服务对象.我会将其重命名,PaymentService以区别于您可能存储的有关付款的分类帐条目(这将是一个价值对象).

您的示例并未显示Order该类的内容,但我认为它会包含某些行项目,传递地址和总金额等信息.这是一个value对象.它代表了业务领域的一件事.

值对象在状态上很重,在行为上更轻.可以实现多种实现,但您不太可能希望将一种实现替换为另一种实现.

值依赖注入框架不会创建值对象.它们由您的业务逻辑代码创建.在您的示例中,您使用Guice创建所有对象.我希望实际上你需要Order根据用户输入创建at运行时.

服务对象可以依赖于值对象,但从不相反.我认为你应该寻求实施:

checkoutService.payfor( order, method );

并不是 order.finishOrder( method )

CheckoutService课堂上,你可以选择一个approriate PaymentService并传递order给它.该CheckoutService会采取构造函数的参数PaymentCardPaymentService(相当于你PaymentCardImpl)和CashPaymentService(相当于你PaymentCashImpl).


Ste*_*ers 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();
  }
}
Run Code Online (Sandbox Code Playgroud)

还是为了你自己的练习:付款的东西.你不需要任何Guice代码.剩下的工作由Guice自动完成.如果您开始使用接口,则可以开始使用此处所述的绑定:http://code.google.com/p/google-guice/wiki/GettingStarted.但是,如果您没有任何接口,则无需任何配置即可完成构建.@Inject注释就足够了.

当然这只是一个示例设计.但它显示了使用Guice设置一个很好的解耦Java应用程序是多么容易.


Gen*_*sky 6

使用MapBinder扩展,您可以注入包含在Map. 然后是使用哪个实现的问题。

您将需要一个密钥,在您的情况下,它可能是一个StringorEnumeration并将所有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);
  }
}
Run Code Online (Sandbox Code Playgroud)

然后在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();
  }
}
Run Code Online (Sandbox Code Playgroud)

注意:注入提供程序不适用于javax.inject.Provider,您需要使用com.google.inject.Provider.

通过注入提供者而不是实例,您可以实现延迟实例化和每个实现不同的实例化策略。就像上面的例子一样,PaymentCardImpl是单例,而另一个是每次创建的。您可以使用 guice范围来控制生命周期,甚至可以实现自己的提供程序来完成其他事情。


Sid*_*ani 6

您可以注释要注入的是哪一个.如果您执行命名绑定,它将解决该问题.

见下文:

bind(Payment.class).annotatedWith(Names.named("Card")).to(PaymentCardImpl.class);

bind(Payment.class).annotatedWith(Names.named("Cash")).to(PaymentCashImpl.class);
Run Code Online (Sandbox Code Playgroud)

那么你要注射的地方是:

@Named("Cash") Payment payment 
Run Code Online (Sandbox Code Playgroud)

要么:

@Named("Card") Payment payment
Run Code Online (Sandbox Code Playgroud)