gsdk
gsdk

Reputation: 57

How to use Guice Assisted Inject for this factory pattern?

The following is a factory pattern that I wrote. But, in order to change it into a Provider, Guice documentation is not quite helping.

class ClientA extends AbstractClient {...}
class ClientB extends AbstractClient {...}
class ClientUtil {
        private static AbstractClient client;
        public static AbstractClient getClient(String key) {
            ClientType clientType = ....
            switch(clientType) {
                case ".." : client = new ClientA.Builder()....build();
                            break;
                case "..." :
                default : client= new ClientB.Builder()....build();
            }
            return client;
        }
}
class Application {
        AbstractClient client = ClientUtil.getClient(key);  // here, key is a string which is dynamic
    }

Please provide some suggestions on how this can be written in a Provider format with Guice AssistedInject.

Upvotes: 2

Views: 2992

Answers (3)

cobexer
cobexer

Reputation: 159

Assuming you want to create the Object directly and thus need to use @Assisted you can let Guice create named factories:

import javax.inject.Inject;

// com.google.inject:guice:4.2.2
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
// com.google.inject.extensions:guice-assistedinject:4.2.2
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;

public class StackOverflow {
    public static void main(String[] args) {
        final Injector injector = Guice.createInjector(new GuiceModule());
        Key<ClientFactory> key = Key.get(ClientFactory.class, Names.named(args[0]));
        System.out.println("Client: " +  injector.getInstance(key).create("xxx"));
    }
}

class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        install(new FactoryModuleBuilder().implement(AbstractClient.class, ClientA.class)
            .build(Key.get(ClientFactory.class, Names.named("ClientA"))));
        install(new FactoryModuleBuilder().implement(AbstractClient.class, ClientB.class)
            .build(Key.get(ClientFactory.class, Names.named("ClientB"))));
    }
}

abstract class AbstractClient {
}

class ClientA extends AbstractClient {
    private String key;
    @Inject
    public ClientA(@Assisted String key) {
        this.key = key;
    }
    @Override
    public String toString() {
        return "ClientA [key=" + key + "]";
    }
}

class ClientB extends AbstractClient {
    private String key;
    private Injector injector; // just an example for additional injections
    @Inject
    public ClientB(@Assisted String key, Injector injector) {
        this.key = key;
        this.injector = injector;
    }
    @Override
    public String toString() {
        return "ClientB [key=" + key + "]";
    }
}

interface ClientFactory {
    AbstractClient create(String key);
}

The downside to this approach is that in order to use a dynamic input you need a reference to the Injector and Guice complains that this is very slow - that may not be an issue for you tough. If somebody knows how to replace the direct injector.getInstance call with something better please let me know!

Upvotes: 0

kendavidson
kendavidson

Reputation: 1460

First off, I would definitely agree with @Lesiak. The code client= new ClientB.Builder()....build(); isn't clear as the ellipsis could be any number of fields you're setting on ClientA/B.

But to give you an example of how to use AssistedInject for your particular instance:

class ClientA extends AbstractClient {
    @Inject
    public ClientA(ServiceOne serviceOne,
            ServiceTwo serviceTwo,
            @Assisted MyObject myObject) {
        ...
    }
}

class ClientB extends AbstractClient {
    // Same constructor as ClientA
}

Your factory would then look something like:

interface ClientFactory {
    @Named("ClientA") public AbstractClient getClientA(...);
    @Named("ClientB") public AbstractClient getClientB(...);
} 

Your parameters can be different objects, or whatever you want, but they essentially have to matchup with the constructor @Assisted annotation. You can see now why @Lesiak provided the answer he did, if your builder is setting 10 fields on ClientA, then your factory method will need to have 10 method parameters, and is very unruly.

You'd then use this with:

@Inject ClientFactory clientFactory;

...
AbstractClient client = clientFactory.getClientA(something, something1, ...);
...

Upvotes: 1

Lesiak
Lesiak

Reputation: 25956

Did you try to write your Factory manually? The manual you linked has a nice example, and your code will translate easily to Guice.

public interface ClientFactory {
    AbstractClient create(String key);
}

public class ClientFactoryImpl implements ClientFactory {
    @Override
    public AbstractClient create(String key) {
        if ("A".equals(key)) {
            return new ClientA();
        } else {
            return new ClientB();
        }
    }
}

and bind factory to implenetation

bind(ClientFactory.class).to(ClientFactoryImpl.class);

AssistedInject is detrimental in your case. All it offers is building ClientFactoryImpl automatically. The implementation only passes injected and assisted arguments to the constructor. But you have some non-trivial logic in the create method. In this case, I suggest you create factory implementation yourself.

Upvotes: 1

Related Questions