Контейнеры IoC / DI, фабрики и создание типов среды выполнения
Недавно я узнал об основах DI Guice и Ninject и хотел использовать их в некоторых моих новых проектах.
Хотя я знаком с общими концепциями внедрения зависимостей и знаю, как использовать эти инфраструктуры для построения графов объектов, я изо всех сил пытаюсь применить IoC, когда дело доходит до динамического поведения приложения.
Рассмотрим этот пример:
- Когда приложение запустится, будет показано главное окно.
- Когда пользователь нажимает на главную панель, открывается контекстное меню.
- В зависимости от выбора пользователя, новый пользовательский элемент управления будет создан и показан в позиции мыши.
- Если пользователь в конце концов решит закрыть приложение, появится окно подтверждения и - после подтверждения - главное окно закроется.
Хотя легко связать View главного окна с Presenter/ViewModel и затем связать его с логикой домена, я не понимаю, как чисто (в смысле IoC) выполнить следующие задачи:
- Динамически создавать конкретный элемент управления UI (например,
IGreenBoxView
,IRedImageView
<-JConcreteGreenBoxView
,JConcreteRedImageView
) без использования какого-либо шаблона поиска службы (например, повторный запрос от IoC)- В зависимости от этого, создайте новую модель, презентатор и просмотр экземпляра.
- Аналогично, создать новое конкретное диалоговое окно, например
JOptionPane
во время выполнения
Я видел некоторые решения с использованием абстрактных фабрик, но, честно говоря, не до конца их понял. Похоже, что такое решение привело бы к тому, что некоторые внутренние типы (домена представления, домена презентатора,...) были открыты корневому каталогу конструкции и, следовательно, всему миру.
Итак, как мне это сделать правильно?
2 ответа
Если вы можете повторно использовать элементы управления, тогда вы можете делать инъекции в конструктор там, где вы их используете. В противном случае вы должны ввести фабрику:
public interface IControlFactory
{
IGreenBoxView CreateGreenBoxView();
IRedImageView CreateRedImageView();
}
и введите его туда, где вам нужно создать этот элемент управления.
Реализация идет в конфигурации контейнера. Там вы можете внедрить контейнер в реализацию. Некоторые контейнеры позволяют реализовать эту фабрику автоматически. например, в Ninject:
Bind<IControlFactory>().ToFactory();
См. https://github.com/ninject/ninject.extensions.factory/wiki
Для тех, кто хочет делать подобные вещи с Unity (вместо Ninject), я создал расширение, которое позволяет вам создавать фабрики, даже не объявляя интерфейсы: UnityMappingFactory @ GitHub
Вы просто добавляете сопоставления там, где вы регистрируете классы во время обычного процесса начальной загрузки...
//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();
//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();
Затем вы просто объявляете интерфейс фабрики сопоставления в конструкторе для CI и используете его метод Create()...
public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }
public TextWidgetViewModel(ITextWidget widget) { }
public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
foreach (IWidget w in data.Widgets)
children.Add(factory.Create(w));
}
В качестве дополнительного бонуса любые дополнительные зависимости в конструкторе отображаемых классов также будут разрешаться при создании объекта. (Также поделился как ответ на этот пост Stackru)