Контейнеры IoC/DI, заводы и создание типа времени выполнения
Недавно я узнал об основах DI Guice и Ninject и хотел использовать их в некоторых из моих новых проектов.
В то время как я знаком с концепциями внедрения общих зависимостей и знаю, как использовать эти фреймворки для построения графиков объектов, я изо всех сил стараюсь применять IoC, когда речь заходит о динамическом поведении приложений.
Рассмотрим следующий пример:
- При запуске приложения будет показано главное окно.
- Когда пользователь нажимает на основную панель, открывается контекстное меню.
- В зависимости от выбора пользователя пользовательский элемент новый будет создан и показан в позиции мыши.
- Если пользователь в конце концов решит закрыть приложение, появится окно подтверждения и - после подтверждения - основное окно будет закрыто.
Пока легко подключить главное окно View к Presenter/ViewModel, а затем привязать его к логике домена, я не понимаю, как чисто (в смысле IoC) достичь следующих задач:
- Динамически создавать конкретное управление пользовательским интерфейсом (например,
IGreenBoxView
, IRedImageView
< - JConcreteGreenBoxView
, JConcreteRedImageView
) без использования какого-либо шаблона локатора службы (например, запрашивая IoC снова)
- В зависимости от этого создайте экземпляр новой модели, презентатора и просмотра
- Similary, создайте новое конкретное диалоговое окно, например.
JOptionPane
во время выполнения
Я видел некоторые решения с использованием абстрактных фабрик, но, честно говоря, не полностью их понимал. Похоже, что такое решение привело бы к тому, что некоторые внутренние (вид домена, ведущего домена,...) внутренние типы будут встраиваться в корень построения и, вместе с тем, во весь мир.
Итак - как мне это сделать?
Ответы
Ответ 1
Если вы можете повторно использовать элементы управления, тогда вы можете делать инъекцию конструктора, где вы их используете. В противном случае вам нужно ввести factory:
public interface IControlFactory
{
IGreenBoxView CreateGreenBoxView();
IRedImageView CreateRedImageView();
}
и введите его там, где вам нужно создать эти элементы управления.
Реализация идет в конфигурацию контейнера. Там вы можете вставить контейнер в реализацию. Некоторые контейнеры обеспечивают автоматическое выполнение этого factory. например, в Ninject:
Bind<IControlFactory>().ToFactory();
См. https://github.com/ninject/ninject.extensions.factory/wiki
Ответ 2
Для тех, кто хочет делать подобные вещи с помощью 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>();
Затем вы просто объявляете интерфейс отображения factory в конструкторе для 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));
}
В качестве дополнительного бонуса любые дополнительные зависимости в конструкторе отображенных классов также будут устранены при создании объекта. (Также поделился как ответ на этот fooobar.com/info/3813/...)