1

I've been using guice for a project.

I have an abstract class which has many implementations. To use the right implementation I use a factory that receives a parameter and then returns the right instance.

Demo Code

@Singleton
public class MyFactory {

    private final Foo foo;

    @Inject
    public MyFactory(final Foo foo) {
        this.foo = foo;
    }

    public A create(final Bar bar) {
        switch (bar) {
            case 0:
                return new B(foo, bar);
            case 1:
                return new C(foo, bar);
            //this goes on
        }
    }
}

public abstract A {
    public A(final Bar bar) {
        //do sth
    }
}

public B extends A {
    private final Foo foo;

    public B(final Foo foo, final Bar bar) {
        super(bar);
        this.foo = foo;
    }
}

public C extends A {
    private final Foo foo;

    public C(final Foo foo, final Bar bar) {
        super(bar);
        this.foo = foo;
    }
}

What I want to know, if I can replace the factory with Guice to inject directly the implementations of A (note that they should use assisted injection)?

Thanks.

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
Juan
  • 117
  • 15

1 Answers1

1

You will still need MyFactory to choose an implementation based on your id, though your assisted injection can be very short.

// No need for this to be @Singleton;
// if you want the same shared Foo instance, make it @Singleton
public class MyFactory {

    private final B.Factory bFactory;
    private final C.Factory cFactory;

    @Inject
    public MyFactory(B.Factory bFactory, C.Factory cFactory) {
        this.bFactory = bFactory;
        this.cFactory = cFactory;
    }

    public A create(final Bar bar) {
        switch (bar.getSomeInteger()) {   // I assume you're checking a
                                          // property of bar
            case 0:
                return bFactory.create(bar);
            case 1:
                return cFactory.create(bar);
            //this goes on
        }
    }
}

public B extends A {
    public interface Factory {
        B create(Bar bar);
    }

    private final Foo foo;

    public B(final Foo foo, @Assisted final Bar bar) {
        super(bar);
        this.foo = foo;
    }
}

public C extends A {
    public interface Factory {
        C create(Bar bar);
    }

    private final Foo foo;

    public C(final Foo foo, @Assisted final Bar bar) {
        super(bar);
        this.foo = foo;
    }
}

And your Module:

public class YourModule extends AbstractModule {
  @Override public void configure() {
    install(new FactoryModuleBuilder().build(B.Factory.class));
    install(new FactoryModuleBuilder().build(C.Factory.class));
  }
}

Edit: You do not need a call to implement on FactoryModuleBuilder in my example, because B.Factory has a create method that returns your subclass B. If you wanted the method to return your superclass A, hiding the concrete type, you could do that; then you would need the implement call, because Guice wouldn't know which constructor to try to call.

If you wanted to force consumers to code to the implementation, you might want to refer to a factory that only returns the interface. That would generally be a good idea to hide the implementation details, and would probably involve creating A.Factory with method A create(Bar bar) and wiring it up with implement. However, that is unnecessary here because your MyFactory already returns A and hides the implementing subclass (acting like A.Factory with logic), and because you would need @Named or some other qualifier annotation to distinguish between the two A.Factory bindings you're creating. In short, that's extra complication with no benefit for this specific case.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • So there's no way guice can help me get ride of that factory due it has logic in it? And thanks for the reply. Btw why wouldn't you make the factory singleton? – Juan Jun 09 '18 at 21:32
  • 1
    You could use [Multibindings](https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/multibindings/MapBinder.html), but one way or another you need to represent a mapping from `int` to A, and Guice has no way to infer that mapping for you. And if you make the Factory Singleton, it will never be garbage collected (more memory) and will be synchronized in its creation (synchronization overhead) so that you are guaranteed to get the same instance. On a desktop VM, new objects are cheap; if you don't need it to be the same object, you'll want the speed and memory instead. – Jeff Bowman Jun 10 '18 at 00:21
  • Sorry for asking to many questions, but I got one more: Shouldn't the module install be like this: install(new FactoryModuleBuilder.implement(A.class, B.class).build(B.Factory.class)); – Juan Jun 11 '18 at 12:37
  • Thank you very much – Juan Jun 11 '18 at 16:36