3

I am using Guice and FactoryModuleBuilder. Typically, it is enough to just define an interface of the factory and Guice will automatically inject the implementation.

However, the part that I am struggling is that the methods in a factory uses generics. Suppose I have the following. A base type of constructed instances as defined by the interface.

interface Foo<T> {
    T get();
}

And two implementations of the Foo interface as defined by the two classes below.

class FooA<T> implements Foo<T> {
    @Inject
    FooA(@Assisted Class<T> clazz, @Assisted String s) {...}
}

class FooB<T> implements Foo<T> {
    @Inject
    FooB(@Assisted Class<T> clazz, @Assisted Integer i) {...}
}

Then I have the factory interface as defined below, using two custom binding annotations that allows me to use multiple implementations.

interface FooFactory {
    @A Foo<T> build(Class<T> clazz, String s);
    @B Foo<T> build(Class<T> clazz, Integer i);
}

I have tried a number of possible solutions, all but one has worked so far. The solution that worked is to basically write my own implementation of FooFactory as shown below. And in the configure method of the module, bind the interface to the implementation; bind(FooFactory.class).to(FooFactoryImpl.class);

class FooFactoryImpl {
    Foo<T> build(Class<T> clazz, String s) {
        return new FooA(clazz, s):
    }
    Foo<T> build(Class<T> clazz, Integer i) {
        return new FooB(clazz, i);
    }
}

However, I have one issue with this solution. The instances are not created by Guice and thus I lose the null checks that comes with Guice. This is drastically different from my other factories that does not have this problem. This means I have to explicitly write null checks for every implementation of Foo. I would like to avoid that.

The following are some of the solutions I have tried.

Solution 1:

FactoryModuleBuilder fmb = new FactoryModuleBuilder()
    .implement(Foo.class, A.class, FooA.class)
    .implement(Foo.class, B.class, FooB.class);
install(fmb.build(FooFactory.class));

Solution 2:

FactoryModuleBuilder fmb = new FactoryModuleBuilder()
    .implement(TypeLiteral.get(Foo.class), A.class, TypeLiteral.get(FooA.class))
    .implement(TypeLiteral.get(Foo.class), B.class, TypeLiteral.get(FooB.class));
install(fmb.build(TypeLiteral.get(FooFactory.class)));

The sample code is available at GitHub (if anyone is interested).

Christopher Z
  • 899
  • 1
  • 12
  • 32
  • Do your FooA and FooB constructors have any injected instances? Should they? All I see are two @Assisted parameters, which doesn't explain how Guice is helpful here. – Jeff Bowman Mar 31 '15 at 15:26
  • I don't think assisted injection works when the factory methods themselves are generic. But if you make it `FooFactory` it could work. – Tavian Barnes Mar 31 '15 at 18:59
  • @JeffBowman, it is a simplified example. Still, regardless if the constructors have any Guice auto-injected instances, you still want Guice to manage these new instances within its own container. Plus, you get the free null checks :) – Christopher Z Apr 01 '15 at 07:12
  • @TavianBarnes, that would mean the number of factories I have to get would be large. e.g., `FooFactory f1`, `FooFactory f2`, `FooFactory f3`, etc. – Christopher Z Apr 01 '15 at 07:31

1 Answers1

1

To my knowledge, you can't design AssistedInject factories to work in this way. However, it seems to me you're doing too much in one class. Because you have no restrictions on Class<T>, it's clear you aren't using any methods of this class in the constructor, which means, it should be fairly easy to refactor the behavior into a separate class. I know this is a little bit of boilerplate, it's not exactly what you want, but it might look something like this:

interface FooDataFactory {
    @A FooData build(String s);
    @B FooData build(Integer i);
}

public class FooA<T> implements Foo<T> {
    public FooA(FooData data) {
        // You should know what class you need when you're actually calling the constructor.
        // This way you don't even need to pass around Class<T> instances
    }
}

If this approach doesn't work for your use case, let me know and I'll edit to compensate.

durron597
  • 31,968
  • 17
  • 99
  • 158