@Assisted \ @ Использование провайдера при создании объектов в иерархическом дизайне

Этот вопрос о правильном использовании Guice @Assisted и @Provides, а также о том, как это сделать.

Текущий дизайн, на который я ссылаюсь, выглядит примерно так: класс в верхней части иерархии также является единственным классом, который предоставляется клиенту (в основном, общедоступным API), он выглядит примерно так:

public class Manager{
public Manager(int managerId, ShiftFactory sf, WorkerFactory wf);
 // methods ...
}

Как вы, наверное, понимаете, id предоставляется пользователем во время создания (@Assisted?), Но остальные - нет, они просто фабрики.

Класс Manager создает экземпляры класса Shift. Класс Shift создает экземпляры класса Worker.

Теперь, чтобы создать класс Shift, мы используем его конструктор:

public Shift(int managerId, int shiftId, WorkerFactory wf);

shiftId, предоставляемый менеджером, а остальные - это те же объекты из конструктора менеджера.

Для создания Worker мы используем 2 статических фабричных метода (но их можно изменить..):

  public Worker createWorkerTypeA(int shiftId, int workerId)
  public Worker createWorkerTypeB(int shiftId, int workerId)

workerId предоставляется классом Shift. а остальное делегируется от конструктора Shift.

Какой правильный, Guice-y способ сделать это? Куда мне положить @Assisted? @Provides?

Мне бы очень хотелось, чтобы это был пример кода, включая абстрактный модуль, потому что примеры кода, которые я видел до сих пор, до сих пор мне не понятны.

Спасибо

1 ответ

Решение

На высоком уровне вы хотите, чтобы ваши фабрики скрывали предсказуемые зависимости, поэтому вам нужно только указать те, которые изменяются. Тот, у кого есть экземпляр Фабрики, должен передавать только данные, а не фабрики или зависимости. Я представляю интерфейс, как это.

interface ManagerFactory {
  Manager createManager(int managerId);
}
interface ShiftFactory {
  Shift createShift(int managerId, int shiftId);
}
interface WorkerFactory { // The two methods here might be difficult to automate.
  Worker createWorkerA(int managerId, int shiftId, int workerId);
  Worker createWorkerB(int managerId, int shiftId, int workerId);
}

class Manager {
  @Inject ShiftFactory shiftFactory;  // set by Guice, possibly in constructor
  private final int managerId;        // set in constructor

  Shift createShift(int shiftId) {
    shiftFactory.createWorkerA(this.managerId, shiftId);  // or B?
  }
}

class Shift {
  @Inject WorkerFactory workerFactory;  // set by Guice, possibly in constructor
  private final int managerId;          // set in constructor
  private final int shiftId;            // set in constructor

  Worker createWorker(int workerId) {
    shiftFactory.createShift(this.managerId, this.shiftId, workerId);
  }
}

Обратите внимание, что Manager совершенно не заботится о работниках - он не создает их, поэтому, в отличие от вашего вопроса, вам не нужно принимать WorkerFactory только для того, чтобы передать его своему Shift. Это часть привлекательности внедрения зависимости; Вы не должны касаться среднего менеджера (среднегоManager?) со своими зависимостями.

Обратите внимание, что ни один из Factory интерфейсы или реализации даже немного видны вашему общедоступному API за пределами конструкторов. Это детали реализации, и вы можете следовать иерархии объектов, даже не вызывая ее извне.

Теперь, как будет выглядеть реализация ManagerFactory? Может быть так:

class ManualManagerFactory {
  // ShiftFactory is stateless, so you don't have to inject a Provider,
  // but if it were stateful like a Database or Cache this would matter more.
  @Inject Provider<ShiftFactory> shiftFactoryProvider;

  @Override public Manager createManager(int managerId) {
    return new Manager(managerId, shiftFactoryProvider.get());
  }
}

... но это во многом шаблонно, и, возможно, гораздо больше, когда есть много введенных или не введенных параметров. Вместо этого Guice может сделать это за вас, если вы по-прежнему предоставляете интерфейс ManagerFactory и аннотируете конструктор:

class Manager {
  private final ShiftFactory shiftFactory;  // set in constructor
  private final int managerId;              // set in constructor

  @Inject Manager(ShiftFactory shiftFactory, @Assisted int managerId) {
    this.shiftFactory = shiftFactory;
    this.managerId = managerId;
  }

  // ...
}

// and in your AbstractModule's configure method:
new FactoryModuleBuilder().build(ManagerFactory.class);

Вот и все. Guice создает свою собственную реализацию ManagerFactory, основанную на отражениях, считывая тип возвращаемого значения метода Manager, сопоставляя его с аннотациями @Inject и @Assisted и параметрами метода интерфейса, и выясняя его оттуда. Вам даже не нужно звонить implement метод на FactoryModuleBuilder, если Менеджер не был интерфейсом; тогда вам нужно будет сказать Guice, какой конкретный тип создать.


Давайте посмотрим на то же самое с пакетом Google AutoFactory, генерирующим код:

@AutoFactory(
    className = "AutoManagerFactory", implementing = {ManagerFactory.class})
class Manager {
  private final ShiftFactory shiftFactory;  // set in constructor
  private final int managerId;              // set in constructor

  @Inject Manager(@Provided ShiftFactory shiftFactory, int managerId) {
    this.shiftFactory = shiftFactory;
    this.managerId = managerId;
  }

  // ...
}

Почти идентично, верно? Это сгенерирует Java-класс (с исходным кодом, который вы можете прочитать!), Который проверяет класс Manager и его конструкторы, читает аннотации @Provided (nb @Provided является противоположностью @Assisted FactoryModuleBuilder) и делегирует конструктору его комбинацию параметров и введенных полей. Два других преимущества для Auto, который работает с Guice, а также с Dagger и другими JSR-330 Dependency Injection:

  1. Это обычный Java-код без отражения в Guice и его FactoryModuleBuilder; производительность отражения на Android плохая, так что это может быть хорошим приростом производительности.

  2. С генерацией кода вам даже не нужно создавать интерфейс ManagerFactory - без каких-либо параметров для @AutoFactory вы получите final class ManagerFactory { ... } это именно то поведение, которое Guice будет подключать через FactoryModuleBuilder. Конечно, вы можете настроить имя и интерфейсы самостоятельно, что также может помочь вашим разработчикам, поскольку сгенерированный код иногда не очень хорошо подходит для инструментов и IDE.


ОБНОВЛЕНИЕ, чтобы ответить на комментарии:

  • Что касается createWorker: Да, извините, ошибка копирования пасты.
  • Относительно автоматизации: это потому, что ни Assisted Inject, ни AutoFactory не имеют отличного способа делегировать статические методы или работать с конструкторами, которые имеют идентичные вспомогательные (предоставляемые пользователем) аргументы. Это тот случай, когда вам, возможно, придется написать собственную Фабрику.
  • Относительно диспетчера, не нуждающегося в WorkerFactory: единственная причина, по которой Manager должен требовать WorkerFactory, - это если он создает либо ShiftFactory, либо сам Shift, вызывая конструктор. Обратите внимание, что мой пример не выполняет ни одного из них: вы позволяете инфраструктуре внедрения зависимостей (Guice) предоставлять зависимости, что означает, что WorkerFactory скрывается в ShiftFactory, которую Guice уже предоставляет.
Другие вопросы по тегам