java 基于条件的依赖注入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6968474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:04:55  来源:igfitidea点击:

Dependency Injection based on a condition

javadependency-injectionguice

提问by Joeblackdev

I'm using Google Guice for dependency injection. Suppose I have the following:

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

Following on from this, is a very simple module for binding, like so:

继此之后,是一个非常简单的绑定模块,如下所示:

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

As you can see, a Paymentinstance is injected into the Order constructor. This is done in the MyModuleclass and overall is really cool.

如您所见,一个Payment实例被注入到 Order 构造函数中。这是在MyModule课堂上完成的,总体来说真的很酷。

My main looks like:

我的主要看起来像:

public static void main(String[] args) {
    MyModule module = new MyModule();
    Injector injector = Guice.createInjector(module);
    Order order = injector.getInstance(Order.class);
    order.finishOrder();
}

What I can't see however, is how I could incorporate some way to conditionally bind either a PaymentCardImplora PaymentCashImplinstance to the Orderconstructor.

我却无法看到的,是我怎么可能把一些方法来有条件地绑定无论是PaymentCardImpl还是一个PaymentCashImpl实例的Order构造函数。

Let's say for example, that the order was an 'online' order. I would then need to this:

例如,假设订单是“在线”订单。然后我需要这样做:

bind(Payment.class).to(PaymentCardImpl.class);

What's the best way to do this? I'm new to dependency injection.

做到这一点的最佳方法是什么?我是依赖注入的新手。

采纳答案by Sid Malani

You can annotate which one you want to inject. If you do a named binding it will resolve the issue.

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

See below:

见下文:

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

bind(Payment.class).annotatedWith(Names.named("Cash")).to(PaymentCashImpl.class);

Then where you want to inject you do:

然后你想注入你做什么:

@Named("Cash") Payment payment 

or:

或者:

@Named("Card") Payment payment

回答by WW.

Dependency injection is useful for creating servicestyle objects. These have the following characteristics:-

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

  • multiple implementations are likely,
  • heavy on behaviour,
  • internal state is limited to their dependencies and they are generally not mutable
  • would map to an actor in the real world (e.g. cashier), rather than a thing
  • 可能有多种实现,
  • 重在行为,
  • 内部状态仅限于它们的依赖项,它们通常是不可变的
  • 将映射到现实世界中的演员(例如收银员),而不是事物

Based on this, Paymentis a service object. I would rename it PaymentServiceto distinguish it from the ledger entry you may store about a payment (which would be a value object).

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

You example does not show much about what the Orderclass, does, but I assume it would hold information like some line items, delivery address and total amount. This is a valueobject. It represents a thing in the business domain.

你的例子没有显示太多关于Order类的作用,但我认为它会保存一些信息,比如一些行项目、交货地址和总金额。这是一个value对象。它代表业务领域中的事物。

Value objects are heavy on state, and lighter on behaviour. Multiple implementations are possible, but you are less likely to want to replace one implementation with another.

值对象重状态,轻行为。多种实现是可能的,但您不太可能想用另一种实现替换一种实现。

Value objects are not created by your dependency injection framework. They are created by your business logic code. In your example, you are creating all the objects using Guice. I expect in reality you would need to create the Orderat run-time based on user input.

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

Service objects can depend on value objects, but never the other way around. I think you should be looking to implement:

服务对象可以依赖于值对象,但不能反过来。我认为您应该寻求实施:

checkoutService.payfor( order, method );

checkoutService.payfor( order, method );

and not order.finishOrder( method )

并不是 order.finishOrder( method )

Within the CheckoutServiceclass, you could choose an approriate PaymentServiceand pass the orderto it. The CheckoutServicewould take as constructor arguments PaymentCardPaymentService(equivalent to your PaymentCardImpl) and CashPaymentService(equivalent to your PaymentCashImpl).

CheckoutService班级中,您可以选择一个合适的PaymentService并将 传递order给它。该CheckoutService会采取构造函数的参数PaymentCardPaymentService(相当于你PaymentCardImpl)和CashPaymentService(相当于你PaymentCashImpl)。

回答by Stefan Schubert-Peters

I know why you want to do this. But I would not mix up construction code (which is dependency injection configuration) with business logic. If you do that, your business logic might not be understandable anymore. And to me it seems, that your conditional injection depends on the situation, i.e. an input from a user interface.

我知道你为什么要这样做。但我不会将构造代码(即依赖注入配置)与业务逻辑混为一谈。如果你这样做,你的业务逻辑可能不再被理解。在我看来,您的条件注入取决于情况,即来自用户界面的输入。

So why not just inject both and make the condition explicit? I'd prefer that. An example app:

那么为什么不只注入两者并使条件明确呢?我宁愿那样。一个示例应用程序:

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();
  }
}

Still for your own practise: The Payment stuff. You do not need any Guice code in there. The rest is done by Guice automagically. If you start using interfaces you would have start to use bindings as described here: http://code.google.com/p/google-guice/wiki/GettingStarted. But if you do not have any interfaces, the construction is done without any configuration. The @Inject annotations are enough.

仍然为您自己的做法:付款的东西。您在那里不需要任何 Guice 代码。其余的由 Guice 自动完成。如果您开始使用接口,您将开始使用如下所述的绑定:http: //code.google.com/p/google-guice/wiki/GettingStarted。但是如果你没有任何接口,那么构建是不需要任何配置的。@Inject 注释就足够了。

Of course this is just an example design. But it shows how easy it is to setup a nicely decoupled Java app with Guice.

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

回答by Geno Roupsky

Using MapBinderextension you can get injected with several bindings contained in a Map. Then it's a matter of implementation which one to use.

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

You will need a key, in your case that could be a Stringor Enumerationand bind all Paymentimplementations to the appropriate keys:

您将需要一个密钥,在您的情况下,它可能是一个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);
  }
}

Then in Orderyou get injected with the whole map and decide which implementation to use:

然后在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();
  }
}

Note:Injecting providers doesn't work with javax.inject.Provider, you need to use com.google.inject.Provider.

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

By injecting providers instead of instances you can achieve lazy instantiation and different instantiation policies per implementation. Like in the example above the PaymentCardImplis singleton, while the other is created each time. You can use guice scopesto control the lifespan, or even implement your own provider to accomplish something else.

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