Замена абстрактной фабрики на 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));

И затем соответствующим образом свяжите того поставщика информации, которого вы хотите использовать.

Я думаю. Я довольно новичок, чтобы обманывать себя.

Другие вопросы по тегам