Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection based on a condition

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

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 Payment instance is injected into the Order constructor. This is done in the MyModule class and overall is really cool.

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 PaymentCardImpl or a PaymentCashImpl instance to the Order constructor.

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.

like image 779
Joeblackdev Avatar asked Aug 06 '11 17:08

Joeblackdev


4 Answers

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

You will need a key, in your case that could be a String or Enumeration and bind all Payment implementations to the appropriate keys:

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 Order you get injected with the whole map and decide which implementation to use:

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.

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

like image 142
Geno Roupsky Avatar answered Nov 07 '22 22:11

Geno Roupsky


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
like image 27
Sid Malani Avatar answered Nov 07 '22 21:11

Sid Malani


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

  • 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, Payment is a service object. I would rename it PaymentService to distinguish it from the ledger entry you may store about a payment (which would be a value object).

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

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 Order at run-time based on user input.

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

and not order.finishOrder( method )

Within the CheckoutService class, you could choose an approriate PaymentService and pass the order to it. The CheckoutService would take as constructor arguments PaymentCardPaymentService (equivalent to your PaymentCardImpl) and CashPaymentService (equivalent to your PaymentCashImpl).

like image 12
WW. Avatar answered Nov 07 '22 21:11

WW.


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.

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

like image 9
Stefan Schubert-Peters Avatar answered Nov 07 '22 20:11

Stefan Schubert-Peters