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

后端 未结 1 879
你的背包
你的背包 2020-12-24 09:51

A standard object factory may look like this:

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

class IFactory {
    I getI(int          


        
相关标签:
1条回答
  • 2020-12-24 10:49

    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();
      }
    }
    
    0 讨论(0)
提交回复
热议问题