Java – GoF standard factory pattern using Guice

factory-patternguicejava

I have used the standard factory pattern method before to create instances of classes (implementing a specific interface) using a Factory class, which has a "create" method, that returns the right instance based on the parameter passed to it (example snippet given below):

public class SimpleFactory {
    public static SimpleObjectInterface getSimpleObject(int data) {
         SimpleObjectInterface toReturn;
          switch(data) {
           case 1:
            toReturn = new OneSimpleObject();
           break;
          case 2:
            toReturn = new TwoSimpleObject();
          break;
          default:
             toReturn = new DefaultSimpleObject();    
          break;  
        }
        return toReturn;
      }
}

Now I am using Guice in my project for dependency injection. My question is how can I achieve something like the above using Guice? Which implementation instance is needed is decided at runtime based on some user input.

I have looked at Provider and @Named annotations. But I don't understand how exactly it will help me.

Best Answer

In general for the problem where you want a factory that injects most dependencies, but still allows some client-supplied deps, you would use Factories by Assisted Injection.

However in your case this would lead to conditional logic in your factory, which is probably not ideal (it is explicitly discouraged in Guice modules).

I think for your situation a MapBinder would be ideal, and you wouldn't need a factory at all, since you're only switching on data type and not building anything really. In your module you configure a map of int (in your case) keys to impls of SimpleObjectInterface. Then in your main runtime class you inject the map, and when you need an instance of a simple object and have int data available, you call get(data) on the injected map.

I don't have an IDE on this machine, so I can't test the code, but from memory it would be something like below:

In your module:

public class MyModule extends AbstractModule {
  protected void configure() {
    MapBinder<Integer, SimpleObjectInterface> mapbinder
        = MapBinder.newMapBinder(binder(), Integer.class, SimpleObjectInterface.class);
    mapbinder.addBinding(1).toClass(OneSimpleObject.class);
    mapbinder.addBinding(2).toClass(TwoSimpleObject.class);
  }
}

In your app code:

@Inject
private Map<Integer, SimpleObjectInterface> simpleObjectMap;

...

void applicationCode() {
  ...
  Integer data = getData();
  SimpleObjectInterface simpleObject = simpleObjectMap.get(data);
  ...
}

Only issue here is you can't have the "default" binding that you had in your switch statement. Not sure of the best way to handle that, maybe you could assign a default impl in your app code if the object is still null after trying to instantiate it from the map binder. Or you could go back to assisted inject with conditional logic, but it's not really "assisted" injection if the sole dependency is client supplied.

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

Related Topic