Search code examples
javaguiceassisted-inject

Chaining assisted injected parameters using guice-assistedinject


I'm using Guice Assisted Inject library to build a factory for me. I currently have it set up like this:

class MyObject {
  @Inject public MyObject(@Assisted FirstDep first, @Assisted SecondDep second, ThirdDep third) { /**/ }
}

class FirstDep { /* nothing to see here */ }
class SecondDep {
  @Inject public SecondDep(@Assisted FirstDep first) { /**/ }
}
class ThirdDep { /* nothing to see here either */ }

class MyModule extends AbstractModule {
  @Override public void configure() {
    bind(ThirdDep.class);
    install(new FactoryModuleBuilder().build(MyObjectFactory.class));
  }
}

interface MyObjectFactory {
  SecondDep createSecond(@Assisted FirstDep first);
  MyObject createMyObject(@Assisted FirstDep first, @Assisted SecondDep second);
}

This forces me to explicitly create a SecondDep using factory.createController(first, factory.createSecond(first)). Is it possible to change my bindings so I can simply do factory.createController(first), which automatically uses the SecondDep binding and the argument I passed in?


Solution

  • Consider the API you're creating here, especially regarding future extensions or implementations. You evidently have a number of stateful FirstDep instances around, because the SecondDep depends on the particular FirstDep, but a future SecondDep may not depend on that same FirstDep (or any FirstDep at all). The fact that createMyObject(first) can be a shorthand to createMyObject(first, factory.createSecond(first)) is specific to your business case, and I don't think there's a shorthand in Guice to make that assumption.

    That said, you have two options. One, you can create a very small helper:

    // Encapsulate the createMyObject(first) shorthand.
    class MyObjectHelper {
      @Inject MyObjectFactory rawFactory;
    
      MyObject createMyObject(FirstDep first) {
        return rawFactory.createMyObject(first, rawFactory.createSecond(first));
      }
    }
    

    Or, two, you can use @AssistedInject to have Guice effectively overload the constructor:

    class MyObject {
      // Multiple @AssistedInject constructors, selected based on which parameters
      // are marked with @Assisted.
    
      @AssistedInject public MyObject(@Assisted FirstDep first,
           SecondFactory secondFactory, ThirdDep third) {
        this(first, secondFactory.createSecond(first), third);
      }
    
      @AssistedInject public MyObject(@Assisted FirstDep first,
          @Assisted SecondDep second, ThirdDep third) { /**/ }
    }
    
    interface SecondFactory {
      // Note that @Assisted annotations are not needed here. Every parameter in
      // these interfaces is for assisted injection, by definition.
      SecondDep createSecond(FirstDep first);
    }
    
    interface MyObjectFactory {
      MyObject createMyObject(FirstDep first);
      MyObject createMyObject(FirstDep first, SecondDep second);
    }
    

    Though it's slightly more verbose to create a separate factory per class, I think you'll find it to be helpful in keeping your classes/factories separate and easy-to-follow, and it neatly avoids a potential circular-reference that I can't remember offhand if Guice supports. I tend to expose my factories as nested interfaces:

    class SecondDep {
      interface Factory {
        SecondDep create(FirstDep first);
      }
    
      @Inject public SecondDep(@Assisted FirstDep first) { /**/ }
    }
    

    ...which then lets you find and update Second.Factory exactly adjacent to the class it supports.