@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:
Это обычный Java-код без отражения в Guice и его FactoryModuleBuilder; производительность отражения на Android плохая, так что это может быть хорошим приростом производительности.
С генерацией кода вам даже не нужно создавать интерфейс ManagerFactory - без каких-либо параметров для @AutoFactory вы получите
final class ManagerFactory { ... }
это именно то поведение, которое Guice будет подключать через FactoryModuleBuilder. Конечно, вы можете настроить имя и интерфейсы самостоятельно, что также может помочь вашим разработчикам, поскольку сгенерированный код иногда не очень хорошо подходит для инструментов и IDE.
ОБНОВЛЕНИЕ, чтобы ответить на комментарии:
- Что касается createWorker: Да, извините, ошибка копирования пасты.
- Относительно автоматизации: это потому, что ни Assisted Inject, ни AutoFactory не имеют отличного способа делегировать статические методы или работать с конструкторами, которые имеют идентичные вспомогательные (предоставляемые пользователем) аргументы. Это тот случай, когда вам, возможно, придется написать собственную Фабрику.
- Относительно диспетчера, не нуждающегося в WorkerFactory: единственная причина, по которой Manager должен требовать WorkerFactory, - это если он создает либо ShiftFactory, либо сам Shift, вызывая конструктор. Обратите внимание, что мой пример не выполняет ни одного из них: вы позволяете инфраструктуре внедрения зависимостей (Guice) предоставлять зависимости, что означает, что WorkerFactory скрывается в ShiftFactory, которую Guice уже предоставляет.