In my guice module I have multiple factories like shown below:
install(new FactoryModuleBuilder().implement(SportsCar.class,Ferrari.class).build(FerrariFactory.class));
install(new FactoryModuleBuilder().implement(LuxuryCar.class,Mercedes.class).build(MercedesFactory.class));
Both the factories have the following create method which takes an assisted element:
Ferrari create(@Assisted Element partsElement);
Mercedes create(@Assisted Element partsElement);
In a CarChooser class, I get instances of Ferrari or Mercedes as shown below:
@Inject
public CarChooser(FerrariFactory ferrariFactory , MercedesFactory mercedesFactory )
{
this.ferrariFactory = ferrariFactory;
this.mercedesFactory = mercedesFactory;
}
In the same class:
if(type.equals("ferrari"))
ferrariFactory.create(partsElement);
else if (type.equals("mercedes"))
mercedesFactory.create(partsElement);
...
Now, what I am trying is to make this CarChooser class open for extension but closed for modification. i.e. If I need to add another Factory, I shouldn't have to declare it as a variable + add it to the constructor + add another if clause for the corresponding new type. I was planning to use ServiceLoader here and declare an interface CarFactory which will be implemented by all factories (such as FerrariFactory, MercedesFactory etc.) and all implementations will have a getCarType method. But how can I call the create method using Service Loader ?
ServiceLoader<CarFactory> impl = ServiceLoader.load(CarFactory.class);
for (CarFactory fac: impl) {
if (type.equals(fac.getCarType()))
fac.create(partsElement);
}
}
Is the right way if it works (I am not even sure if this would work). Or Is there a better way of doing the same ?
Thanks to the first comment on the post, I know that I want to use MapBinder . I wrote a CarFactory which is extended by both FerrariFactory and MercedesFactory. So I add the following:
MapBinder<String, CarFactory> mapbinder = MapBinder.newMapBinder(binder(), String.class, CarFactory.class);
mapbinder.addBinding("Ferrari").to(FerrariFactory.class);
mapbinder.addBinding("Mercedes").to(MercedesFactory.class);
But since the .to portion of the above code is abstract class I get an initialisation error that FerrariFactory is not bound to any implementation. What should I have here to bind it to the correct Assisted Inject Factory declared with the FactoryModuleBuilder ?
So, using a MapBinder along with generics is the solution.
install(new FactoryModuleBuilder().implement(SportsCar.class,Ferrari.class).build(FerrariFactory.class));
install(new FactoryModuleBuilder().implement(LuxuryCar.class,Mercedes.class).build(MercedesFactory.class));
MapBinder<String, CarFactory<?>> mapbinder = MapBinder.newMapBinder(binder(), new TypeLiteral<String>(){}, new TypeLiteral<CarFactory<?>>(){});
mapbinder.addBinding("ferrari").to(FerrariFactory.class);
mapbinder.addBinding("mercedes").to(MercedesFactory.class);
The important thing here to note is that this seems to be supported only in Guice 3.0 + JDK 7. For JDK 8, you need Guice 4.0 ! Found this problem on https://github.com/google/guice/issues/904
Hope that helps.
More details regarding the solution:
http://crusaderpyro.blogspot.sg/2016/07/google-guice-how-to-use-mapbinder.html