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

Другие вопросы по тегам