dependency-injection пример - Есть ли шаблон для инициализации объектов, созданных с помощью контейнера DI





java symfony (5)


Я также сталкивался с этой ситуацией несколько раз в средах, где я динамически создаю объекты ViewModel на основе объектов Model (очень хорошо очерченный этой другой статьей Stackoverflow ).

Мне понравилось, как расширение Ninject, которое позволяет динамически создавать заводы на основе интерфейсов:

Bind<IMyFactory>().ToFactory();

Я не мог найти подобную функциональность непосредственно в Unity ; поэтому я написал собственное расширение для IUnityContainer, которое позволяет вам регистрировать фабрики, которые будут создавать новые объекты на основе данных из существующих объектов, по существу сопоставляющих одну иерархию типов с другой иерархией типов: [email protected]

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

//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));
}

В качестве дополнительного бонуса любые дополнительные зависимости в конструкторе отображенных классов также будут устранены при создании объекта.

Очевидно, что это не решит каждую проблему, но пока это мне очень понравилось, поэтому я решил поделиться ею. На сайте проекта есть дополнительная документация на сайте GitHub.

Я пытаюсь заставить Unity управлять созданием моих объектов, и я хочу иметь некоторые параметры инициализации, которые неизвестны до времени выполнения:

На данный момент единственный способ, которым я мог думать о том, как это сделать, - это использовать метод Init на интерфейсе.

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

Затем, чтобы использовать его (в Unity), я бы сделал следующее:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

В этом случае параметр runTimeParam определяется во время выполнения на основе пользовательского ввода. Тривиальный случай здесь просто возвращает значение runTimeParam но на самом деле параметр будет чем-то вроде имени файла, а метод initialize сделает что-то с файлом.

Это создает ряд проблем, а именно, что метод Initialize доступен на интерфейсе и может быть вызван несколько раз. Установка флага в реализации и исключение бросания при повторном вызове Initialize кажется неуклюжим.

В тот момент, когда я разрешаю свой интерфейс, я не хочу ничего знать о реализации IMyIntf . Однако я хочу знать, что для этого интерфейса нужны определенные параметры инициализации одного времени. Есть ли способ каким-то образом аннотировать (атрибуты?) Интерфейс с этой информацией и передавать их в фреймворк при создании объекта?

Изменить: описал интерфейс немного больше.




В любом месте, где вам нужно значение времени выполнения для построения конкретной зависимости, абстрактная фабрика является решением.

Инициализация методов на интерфейсах пахнет липкой абстракцией .

В вашем случае я бы сказал, что вы должны моделировать интерфейс IMyIntf на том, как вам нужно его использовать, а не как вы намерены создавать его реализации. Это деталь реализации.

Таким образом, интерфейс должен быть просто:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

Теперь определим абстрактную фабрику:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

Теперь вы можете создать конкретную реализацию IMyIntfFactory которая создает конкретные экземпляры IMyIntf подобные этому:

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

Обратите внимание, что это позволяет нам защищать инварианты класса с помощью ключевого слова readonly . Нет вонючих Инициализация методов.

Реализация IMyIntfFactory может быть простой:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

Во всех ваших потребителях, где вам нужен экземпляр IMyIntf , вы просто IMyIntfFactory от IMyIntfFactory , запросив его через IMyIntfFactory конструктора .

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




Обычно, когда вы сталкиваетесь с этой ситуацией, вам нужно пересмотреть свой дизайн и определить, смешиваете ли вы объекты состояния / данных с вашими чистыми сервисами. В большинстве (не всех) случаях вы захотите разделить эти два типа объектов.

Если вам нужен параметр, специфичный для контекста, переданный в конструкторе, одним из вариантов является создание фабрики, которая разрешает ваши зависимостей между службами через конструктор и принимает ваш параметр времени выполнения в качестве параметра метода Create () (или Generate () ), Build () или все, что вы называете фабричными методами).

Как правило, создание сеттеров или метод Initialize () являются плохим дизайном, так как вам нужно «запомнить» их, чтобы они не открывали слишком много состояния вашей реализации (то есть, что остановить кого-либо из - инициализировать инициализацию или сеттер?).




Думаю, я решил это, и он чувствует себя довольно здоровым, так что он должен быть наполовину правдой :))

Я разделил IMyIntf на «getter» и «setter» интерфейсы. Так:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

Затем реализация:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize() все еще можно вызывать несколько раз, но используя биты парадигмы Locator Service, мы можем легко ее обернуть, чтобы IMyIntfSetter был почти внутренним интерфейсом, отличным от IMyIntf .




Удалите @Configurable аннотацию из вашего сервлета и добавьте:

SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext (this);

в первой строке вашего метода init ().





dependency-injection inversion-of-control unity-container ioc-container interface-design