I am new to Guice and I was wondering how far I can take it.
I have an interface UserInfo
with multiple implementing classes GoogleUserInfo
, FacebookUserInfo
, TwitterUserInfo
etc. These classes are created using a factory
public class UserInfoFactory {
public UserInfo createFromJsonString(String jsonspec) {
.
.
.
}
}
The creation is controlled by the JSON string jsonspec
which controls which of the implementing classes of UserInfo
is returned. Specifically, there is a JSON string element domain
which controls the creation. The creation really is a function of the deserialization of jsonspec
using GSON.
I was wondering if there is a good way to replace this creation with a Guice dependency injection?
You can integrate Guice into the factory, but your code is probably better off exactly as it is in this case.
This is actually one of those Factories that can't be replaced easily, because it has to contain the logic to parse out the jsonSpec
and change which concrete type it returns based on that. Let's say the slightly-less-simplified version of the factory looks like this:
public class UserInfoFactory {
public UserInfo createFromJsonString(String jsonspec) {
if(getUserType(jsonSpec) == TWITTER) {
return new TwitterUserInfo(jsonSpec);
} else { /* ... */ }
}
private UserInfoType getUserType(String jsonSpec) { /* ... */ }
}
That logic has to live somewhere, and your own UserInfoFactory
seems like a perfect home. However, because you use new
, you would not be able to inject any of TwitterUserInfo
's dependencies, or its dependencies' dependencies--and that is the type of problem that Guice solves nicely.
You can inject TwitterUserInfo
as a Provider
, which will give you access to as many of fully-injected TwitterUserInfo
objects as you'd like:
public class UserInfoFactory {
@Inject Provider<TwitterUserInfo> twitterUserInfoProvider;
public UserInfo createFromJsonString(String jsonspec) {
if(getUserType(jsonSpec) == TWITTER) {
TwitterUserInfo tui = twitterUserInfoProvider.get();
tui.initFromJson(jsonSpec);
return tui;
} else { /* ... */ }
}
}
...and, of course, that also allows you to inject a @Twitter Provider<UserInfo>
if you only need the interface and want to vary the concrete class sometime in the future. If you want TwitterUserInfo
to accept constructor parameters, Assisted injection will help you create a TwitterUserInfoFactory
though, which will help it toward immutability:
public class UserInfoFactory {
@Inject TwitterUserInfo.Factory twitterUserInfoFactory;
public UserInfo createFromJsonString(String jsonspec) {
if(getUserType(jsonSpec) == TWITTER) {
return twitterUserInfoFactory.create(jsonSpec);
} else { /* ... */ }
}
}
// binder.install(new FactoryModuleBuilder().build(TwitterUserInfoFactory.class));
public class TwitterUserInfo implements UserInfo {
public interface Factory {
TwitterUserInfo create(String jsonSpec);
}
public TwitterUserInfo(@Assisted String jsonSpec, OtherDependency dep) { /* ... */ }
}
A final note: TwitterUserInfo
likely doesn't have any dependencies--it sounds like a data object to me--so leaving your class exactly how you found it (with new
) is probably the best approach. While it's nice to have decoupled interfaces and classes to ease testing, there is a cost in maintenance and understanding. Guice is a very powerful hammer, but not everything is actually a nail to be hammered.
You'll probably want to use assisted injection.
You'll need to annotate your constructors in GoogleUserInfo, FacebookUserInfo, and TwitterUserInfo with (@Assisted String jsonspec) (and of course @Inject)
Then you'll need to configure your factory class
binder.install(new FactoryModuleBuilder().build(UserInfoFactory.class));
And then appropriately bind whichever Info provider you want to use.
I think. I'm pretty new to guice myself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With