Замена абстрактной фабрики на Guice?
Я новичок в Guice, и мне было интересно, как далеко я могу это сделать. У меня есть интерфейс UserInfo
с несколькими реализующими классами GoogleUserInfo
, FacebookUserInfo
, TwitterUserInfo
и т. д. Эти классы создаются с использованием фабрики
public class UserInfoFactory {
public UserInfo createFromJsonString(String jsonspec) {
.
.
.
}
}
Создание контролируется строкой JSON jsonspec
который контролирует, какой из классов реализации UserInfo
возвращается В частности, есть строковый элемент JSON domain
который контролирует создание. Создание действительно является функцией десериализации jsonspec
используя GSON.
Мне было интересно, есть ли хороший способ заменить это создание инъекцией зависимостей Guice?
2 ответа
Вы можете интегрировать Guice в фабрику, но ваш код, вероятно, лучше, чем в этом случае.
На самом деле это одна из тех фабрик, которые не могут быть легко заменены, потому что она должна содержать логику для анализа jsonSpec
и изменить, какой конкретный тип он возвращает на основе этого. Допустим, слегка упрощенная версия фабрики выглядит так:
public class UserInfoFactory {
public UserInfo createFromJsonString(String jsonspec) {
if(getUserType(jsonSpec) == TWITTER) {
return new TwitterUserInfo(jsonSpec);
} else { /* ... */ }
}
private UserInfoType getUserType(String jsonSpec) { /* ... */ }
}
Эта логика должна где-то жить, и ваша собственная UserInfoFactory
кажется идеальным домом. Тем не менее, потому что вы используете new
вы не сможете ввести какой-либо из TwitterUserInfo
зависимости или зависимости - это тип проблемы, который хорошо решает Guice.
Вы можете ввести TwitterUserInfo
как Provider
, который даст вам доступ ко многим полностью введенным TwitterUserInfo
объекты, как вы хотели бы:
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 { /* ... */ }
}
}
... и, конечно, это также позволяет вводить @Twitter Provider<UserInfo>
если вам нужен только интерфейс и вы хотите изменить конкретный класс в будущем. Если ты хочешь TwitterUserInfo
чтобы принять параметры конструктора, вспомогательная инъекция поможет вам создать TwitterUserInfoFactory
хотя, что поможет ему к неизменности:
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) { /* ... */ }
}
Последнее замечание: TwitterUserInfo
скорее всего, не имеет никаких зависимостей - для меня это звучит как объект данных - так что оставьте ваш класс именно таким, каким вы его нашли (с new
), вероятно, лучший подход. Хотя было бы неплохо разделить интерфейсы и классы, чтобы упростить тестирование, затраты на обслуживание и понимание затратны. Guice - очень мощный молот, но не все - это гвоздь, который нужно забивать.
Вот мой вариант абстрактного шаблона фабрики с Guice - использование множественных привязок для привязки конкретных фабрик.
Здесь я использую набор фабрик и цикл для выбора подходящего класса фабрики, хотя вы также можете использовать карту и получить конкретную фабрику по некоторому ключу, если это возможно.
public class AbstractFactory {
@Inject Set<UserInfoFactory> factories;
public UserInfoFactory createFor(String jsonSpec) {
for (UserInfoFactory factory : factories) {
if (factory.handles(jsonSpec))
return factory;
}
throw new IllegalArgumentException("No factory for a given spec!");
}
}
Этот метод не требует, чтобы вы открывали и изменяли класс AbstractFactory для добавления новой конкретной фабрики - просто напишите новую фабрику и добавьте соответствующую привязку к вашему модулю Guice.
Каждая конкретная фабрика отвечает за обработку определенного формата данных и за правильный синтаксический анализ строки json:
public class FacebookUserInfoFactory implements UserInfoFactory {
public boolean handles(String jsonSpec) {
return jsonSpec.contains("facebook");
}
public UserInfo createFromJsonString(String jsonspec) {
return new FacebookUserInfo(jsonspec);
}
}
public class TwitterUserInfoFactory implements UserInfoFactory {
public boolean handles(String jsonSpec) {
return jsonSpec.contains("twitter");
}
public UserInfo createFromJsonString(String jsonspec) {
return new TwitterUserInfo(jsonspec);
}
}
Теперь свяжите свои бетонные заводы с помощью Multibinder. Пример использования:
AbstractFactory abstractFactory = Guice
.createInjector(new AbstractModule() {
protected void configure() {
Multibinder<UserInfoFactory> factoryBindings = Multibinder
.newSetBinder(binder(), UserInfoFactory.class);
factoryBindings.addBinding().to(FacebookUserInfoFactory.class);
factoryBindings.addBinding().to(TwitterUserInfoFactory.class);
}
}).getInstance(AbstractFactory.class);
UserInfoFactory factory1 = abstractFactory.createFor("twitter");
UserInfoFactory factory2 = abstractFactory.createFor("facebook");
UserInfo user1 = factory1.createFromJsonString("twitter user json string");
UserInfo user2 = factory2.createFromJsonString("facebook user json string");
System.out.println(user1.toString());
System.out.println(user2.toString());
}
Результатом будет:
TwitterUserInfo{twitter user json string}
FacebookUserInfo{facebook user json string}
UserInfo
это просто интерфейс или абстрактный класс (здесь опущен для ясности).
Возможно, вы захотите использовать вспомогательную инъекцию.
Вам нужно будет аннотировать ваши конструкторы в GoogleUserInfo, FacebookUserInfo и TwitterUserInfo с помощью (@Assisted String jsonspec) (и, конечно, @Inject)
Тогда вам нужно будет настроить свой заводской класс
binder.install(new FactoryModuleBuilder().build(UserInfoFactory.class));
И затем соответствующим образом свяжите того поставщика информации, которого вы хотите использовать.
Я думаю. Я довольно новичок, чтобы обманывать себя.