2013-03-15 4 views
2

Я рефакторинг нашего приложения, чтобы включить Dependency Injection (через инъекцию конструктора) и работать в сложный краеугольном случае:Создания экземпляров плагина с помощью контекста DI

В настоящее время мы имеем ImageViewer объектов, когда экземпляр поиска сборки для экземпляров ImageViewerPlugin (абстрактного базового класса), создавая их с помощью отражения. Это делается в конструкторе ImageViewer, используя метод (называемый в цикле для всех конкретных типов подключаемых модулей), который похож на:

private ImageViewerPlugin LoadPlugin(Type concretePluginType) 
{ 
    var pluginConstructor = concretePluginType.GetConstructor(
    BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public, 
    null, 
    new[] { typeof(ImageViewer) }, 
    null); 

    return (ImageViewerPlugin) pluginConstructor.Invoke(
    new object[] { constructorParameter }); 
} 

Класс ImageViewerPlugin выглядит примерно так:

internal ImageViewerPlugin 
{ 
    protected ImageViewer _viewer; 
    protected ImageViewerPlugin(ImageViewer viewer) 
    { 
    _viewer = viewer; 
    } 
} 

конкретная реализация выглядит примерно так:

internal AnImageViewerPlugin 
{ 
    public AnImageViewerPlugin(ImageViewer viewer) : base(viewer) 
    { 
    } 
} 

Каждый ImageViewer экземпляр имеет свою собственную коллекцию ImageViewerPlugin инст Ances.

Теперь, когда приложение реорганизовано для использования контейнера DI и инсталляции конструктора, я обнаруживаю, что эти плагины имеют зависимости (которые ранее были скрыты с использованием глобальных статических классов), которые должны быть разрешены контейнером DI, но я не уверен, как это сделать, не используя Service Locator (анти-шаблон).

Наиболее разумным решением является создание этих экземпляров плагина с использованием DI. Это позволило бы мне добавить дополнительные параметры конструктора, чтобы они зависели от инъецируемых через инсталляцию конструктора. Но если я это сделаю, как я могу передать конкретное значение параметра viewer, а остальные значения введенного значения будут введены?

Я думал, что ImageViewerPluginFactory поможет достичь этого, но не может понять, как реализовать такой завод, поскольку каждый плагин, вероятно, будет иметь другую подпись конструктора.

Как я могу решить этот сценарий? Или я подхожу к этому совершенно неправильно?

ответ

2

Так у вас есть ImageViewer, что зависит от коллекции ImageViewerPlugin экземпляров, каждый из которых зависит от п ImageViewer, которая зависит от сбора ImageViewerPlugin экземпляров, каждый из которых зависит от п ImageViewer, которая зависит от сбора ImageViewerPlugin экземпляров, каждый из которых зависит от ... ну, вы получите изображение :-)

Это круговая ссылка. Оставляя в стороне всю вещь контейнера DI, как бы вы создали эту иерархию, когда делаете это вручную? С инжектором конструктора вы не можете. Это можно видеть из следующего примера:

var plugin = new ImageViewerPlugin([what goes here?]); 
var viewer = new ImageViewer(plugin); 

Вам придется разорвать этот порочный круг зависимости, так или иначе, и общий совет, чтобы возвратиться инъекции собственности в этом случае:

var plugin = new ImageViewerPlugin(); 
var viewer = new ImageViewer(plugin); 

// Property injection 
plugin.Viewer = viewer; 

Но еще больше , вы должны хорошо взглянуть на дизайн приложения, так как циркулярная ссылка часто указывает на проблему в дизайне. Например, внимательно посмотрите, какое поведение требуется плагину от зрителя и какое поведение зрителя нужно от плагинов.Вы можете быть в состоянии извлечь это в другой класс, следующим образом:

var imageServices = new ImageServices(); 
var plugin = new ImageViewerPlugin(imageServices); 
var viewer = new ImageViewer(imageServices, plugin); 

Это полностью решает эту проблему, но это зависит от ситуации, является ли возможным.

С последним решением регистрация с помощью простого инжектора будет довольно прямой. При нарушении зависимости с использованием вложения свойств вы можете использовать метод RegisterInitializer(Action). Это позволяет вам разорвать цикл зависимостей. Например:

container.RegisterInitializer<ImageViewer>(viewer => 
{ 
    foreach (var plugin in viewer.Plugins) 
    { 
     plugin.Viewer = viewer; 
    } 
}); 
+0

Спасибо, Стивен. Я обновил свой вопрос, чтобы показать, как я создаю экземпляры плагина. Я уже подумал о переходе на описанный вами проект с использованием метода RegisterInitializer, однако я считаю, что он вполне действителен для того, чтобы иметь базовый класс плагина, который, как ожидается, получит свой контекст через параметр конструктора. В противном случае чувствуется как (хотя и небольшой) взлом. Обычно плагин запускается в контексте всего приложения, и в этом случае его контекст является неявным. В этом случае он имеет гораздо более ограниченный контекст, поэтому его нужно рассказать, каков его контекст. – Josh

+0

Я посмотрел ваше обновление, но мой совет тот же. Не допускайте смешивания этой циклической ссылки с другими зависимостями в конструкторе. На этом есть [умные «хаки»] (http://stackoverflow.com/questions/15123515/pass-runtime-value-to-constructor-using-simpleinjector), но вместо этого: держите его простым, держите его быстрым. – Steven

+1

Спасибо за ответ. Хотя для меня это не идеально, я собираюсь взять ваш совет и пойти с методом инициализации. – Josh

Смежные вопросы