Как использовать фабрики Guice AssistedInject вместе с загрузчиком сервисов?

В моем модуле guice есть несколько фабрик, как показано ниже:

install(new FactoryModuleBuilder().implement(SportsCar.class,Ferrari.class).build(FerrariFactory.class));
install(new FactoryModuleBuilder().implement(LuxuryCar.class,Mercedes.class).build(MercedesFactory.class));

Обе фабрики имеют следующий метод create, который принимает вспомогательный элемент:

Ferrari create(@Assisted Element partsElement);

Mercedes create(@Assisted Element partsElement);

В классе CarChooser я получаю экземпляры Ferrari или Mercedes, как показано ниже:

@Inject 
public CarChooser(FerrariFactory ferrariFactory , MercedesFactory mercedesFactory )
{
        this.ferrariFactory = ferrariFactory;
        this.mercedesFactory = mercedesFactory;
} 

В том же классе:

if(type.equals("ferrari"))
    ferrariFactory.create(partsElement);
else if (type.equals("mercedes"))
    mercedesFactory.create(partsElement);
...

Теперь, что я пытаюсь сделать, так это сделать этот класс CarChooser открытым для расширения, но закрытым для модификации. т.е. если мне нужно добавить еще один Factory, мне не нужно объявлять его как переменную + добавлять его в конструктор + добавлять еще одно предложение if для соответствующего нового типа. Я планировал использовать здесь ServiceLoader и объявить интерфейс CarFactory, который будет реализован всеми фабриками (такими как FerrariFactory, MercedesFactory и т. д.), и все реализации будут иметь метод getCarType. Но как я могу вызвать метод create с помощью Service Loader?

ServiceLoader<CarFactory> impl = ServiceLoader.load(CarFactory.class);

for (CarFactory fac: impl) {
     if (type.equals(fac.getCarType()))
         fac.create(partsElement);
     }
}

Правильный ли способ, если он работает (я даже не уверен, сработает ли это). Или есть лучший способ сделать то же самое?

Благодаря первому комментарию к публикации я знаю, что хочу использовать MapBinder. Я написал CarFactory, который расширяется как FerrariFactory, так и MercedesFactory. Поэтому я добавляю следующее:

MapBinder<String, CarFactory> mapbinder = MapBinder.newMapBinder(binder(), String.class, CarFactory.class);

mapbinder.addBinding("Ferrari").to(FerrariFactory.class);
mapbinder.addBinding("Mercedes").to(MercedesFactory.class);

Но поскольку часть .to приведенного выше кода является абстрактным классом, я получаю сообщение об ошибке инициализации, что FerrariFactory не привязана к какой-либо реализации. Что мне нужно здесь, чтобы привязать его к правильной фабрике вспомогательных инъекций, объявленной с помощью FactoryModuleBuilder?


person Crusaderpyro    schedule 18.07.2016    source источник
comment
Вот для чего нужны мультибиндинги. Используйте мультибайдер, чтобы привязать Set<CarFactory> в вашем модуле.   -  person Tavian Barnes    schedule 18.07.2016


Ответы (1)


Итак, использование MapBinder вместе с дженериками - это решение.

    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);

Здесь важно отметить, что, похоже, это поддерживается только в Guice 3.0 + JDK 7. Для JDK 8 вам понадобится Guice 4.0! Обнаружил эту проблему на https://github.com/google/guice/issues/904

Надеюсь, это поможет.

Подробнее о решении:

http://crusaderpyro.blogspot.sg/2016/07/google-guice-how-to-use-mapbinder.html

person Crusaderpyro    schedule 30.07.2016