Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Guice automatically create instances of different classes based on a parameter?

A standard object factory may look like this:

interface I { ... }
class A implements I { ... }
class B implements I { ... }

class IFactory {
    I getI(int i) {
        switch (i) {
        case 1: return new A();
        default: return new B();
        }
    }
}

Is it possible to set up bindings so that switch is done for me, i.e. all I do is call getInstance or inject? I was looking at assisted injection but that seems to be different topic: https://code.google.com/p/google-guice/wiki/AssistedInject

like image 658
Schultz9999 Avatar asked Jul 16 '13 06:07

Schultz9999


People also ask

How does Guice dependency injection work?

Using GuiceIn each of your constructors that need to have something injected in them, you just add an @Inject annotation and that tells Guice to do it's thing. Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in.

Is dagger better than Guice?

If you're working on an Android application, reflection is very slow. This means that using Guice could have a noticeable effect on performance and Dagger is probably the right answer for you. If you're working on a Java application, then your options are more open.

What is Guice dependency?

Guice is an open source, Java-based dependency injection framework. It is quiet lightweight and is actively developed/managed by Google. This tutorial covers most of the topics required for a basic understanding of Google Guice and to get a feel of how it works.

How does Google Guice work?

getInstance(...) , Guice doesn't need to do any reflection. It creates the RealBillingService right then, injecting all its (transitive) dependencies, and returns that (not a proxy). If you use Guice's AOP functionality then those objects will be proxies, but otherwise Guice doesn't return proxies.


1 Answers

It sounds like you're looking for a MapBinder, which is part of the Multibindings feature. Note that you'll still need to put in some kind of IFactory or other factory interface, because getInstance doesn't take a parameter the way your getI does, and you'll still need to establish a mapping from integer to class implementation somewhere.

MapBinder-style

class IModule extends AbstractModule {
  @Override public void configure() {
    MapBinder<Integer, I> myBinder =
        MapBinder.newMapBinder(binder(), Integer.class, I.class);
    myBinder.addBinding(1).to(A.class);
    // Add more here.
  }
}

// You can even split the MapBinding across Modules, if you'd like.
class SomeOtherModule extends AbstractModule {
  @Override public void configure() {
    // MapBinder.newMapBinder does not complain about duplicate bindings
    // as long as the keys are different.
    MapBinder<Integer, I> myBinder =
        MapBinder.newMapBinder(binder(), Integer.class, I.class);
    myBinder.addBinding(3).to(C.class);
    myBinder.addBinding(4).to(D.class);
  }
}

An injector configured with those modules will provide an injectable Map<Integer, I> that has an instance of everything bound; here it would be a three-entry map from 1 to a fully-injected A instance, from 3 to a C instance, and from 4 to a D instance. This is actually an improvement over your switch example, which used the new keyword and thus didn't inject any dependencies into A or B.

For a better option that doesn't create so many wasted instances, inject a Map<Integer, Provider<I>> that MapBinder also provides automatically. Use it like this:

class YourConsumer {
  @Inject Map<Integer, Provider<I>> iMap;

  public void yourMethod(int iIndex) {
    // get an I implementor
    I i = iMap.get(iIndex).get();
    // ...
  }
}

To provide a "default" implementation (and opaque interface) the way you did, though, you'll want to implement your own short wrapper on top of the MapBinder map:

class IFactory {
  @Inject Map<Integer, Provider<I>> iMap;
  @Inject Provider<B> defaultI; // Bound automatically for every Guice key

  I getI(int i) {
    return iMap.containsKey(i) ? iMap.get(i).get() : defaultI.get();
  }
}

Simpler, factory-style

If the above looks like overkill, remember that you can inject an Injector and create a local Map from key to implementation. (You can also use ImmutableMap like I did here).

class IFactory {
  @Inject Injector injector; // This is a bad idea, except for times like this
  @Inject Provider<B> defaultI;
  static final ImmutableMap<Integer, Class<? extends I>> map = ImmutableMap.of(
      1, A.class,
      3, C.class,
      4, D.class);

  I getI(int i) {
    return map.containsKey(i)
        ? injector.getInstance(map.get(i))
        : defaultI.get();
  }
}
like image 160
Jeff Bowman Avatar answered Sep 28 '22 12:09

Jeff Bowman