Контейнеры 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/...)