Объединение DI с параметрами конструктора?

Как объединить инъекцию конструктора с параметрами конструктора "вручную"? то есть.

public class SomeObject
{
    public SomeObject(IService service, float someValue)
    {
    }
}

Если IService должен быть разрешен/инъецирован моим контейнером DI, и необходимо указать someValue. Как мне смешать два?

Ответы

Ответ 1

Такие конструкции следует избегать, когда это возможно. Поэтому спросите себя: действительно ли этот параметр необходим как аргумент конструктора? Или может SomeObject быть заменен на безгосударственный, который повторно используется всеми, кто зависит от него, передавая параметр методу, который вы выполняете на объекте?

например. Вместо

public class SomeObject
{
    private float someValue
    public SomeObject(IService service, float someValue)
    {
        this.someValue = someValue
    }

    public float Do(float x)
    {
        return this.Service.Get(this.someValue) * x;
    }
}

использование

public class SomeObject
{
    public SomeObject(IService service)
    {
    }

    public float Do(float x, float someValue)
    {
        return this.Service.Get(someValue) * x;
    }
}

Если требуется перейти на factory:

public interface ISomeObjectFactory
{
    ISomeObject CreateSomeObject(float someValue);
}

public class SomeObjectFactory : ISomeObjectFactory
{
    private IKernel kernel;
    public SomeObjectFactory(IKernel kernel) 
    {
        this.Kernel = kernel;
    }

    public ISomeObject Create(float someValue)
    {
        return this.kernel.Get<ISomeObject>(WithConstructorArgument("someValue", someValue);
    }
}

Предварительный просмотр: Ninject 2.4 больше не потребует реализации, но позволяет

kernel.Bind<ISomeObjectFactory>().ToFactory();  // or maybe .AsFactory();

Ответ 2

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

Наш подход заключается в создании factory через D.I., а метод factory Create будет построен сам, используя переданный в D.I. контейнер. Нам не нужно часто использовать этот шаблон, но когда мы это делаем, он делает продукт намного чище (поскольку он делает наши графики зависимостей меньше).

Ответ 3

Другой подход - инициализация в два этапа (не связанная с ником, любая структура DI):

public class SomeObject
{
    private readonly IService _service;

    public SomeObject(IService service)
    {
        // constructor only captures dependencies
        _service = service;
    }

    public SomeObject Load(float someValue)
    {
        // real initialization goes here
        // ....

        // you can make this method return no value
        // but this makes it more convienient to use
        return this;
    }
}

и использование:

public static class TestClass
{
    public static void TestMethod(IService service)
    {
        //var someObject = new SomeObject(service, 5f);
        var someObject = new SomeObject(service).Load(5f);
    }
}

Ответ 4

Если значение "somevalue" всегда постоянное, вы можете подумать об использовании InjectionParameters, пока вы регистрируете свой тип с контейнером, как он объяснялся в следующей статье

Смотрите здесь

но если это не так, то нет смысла выделять значение параметра при разрешении экземпляра, вы можете подумать о перемещении 'someValue' из конструктора и сделать его свойством класса.

Ответ 5

В NInject, с которым вы отметили это, вы вводите автоматически созданный Factory в форме Func<parameters you wish to feed in,T>, используя FuncModule, как описано в этом сообщении.

Этот подход также доступен в autofac для одного.

Различные методы методов Factory рассматриваются в ответах на этот вопрос.

EDIT: NB Хотя это может быть интересно, используйте решение @Remo Gloor (и критически советуем избегать такого решения)

Ответ 6

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

Мое другое предложение состоит в том, что вместо прямого доступа к нему вы создаете factory, который вы можете использовать для создания такого объекта. Затем вы регистрируете factory в своем контейнере и используете factory для создания своего экземпляра. Что-то вроде этого:

public class SomeObjectFactory : ISomeObjectFactory
{
    private IYourService _service;
    public SomeObjectFactory(IYourService service) 
    {
        _service = service;
    }

    public ISomeObject Create(float someValue)
    {
        return new SomeObject(_service, someValue);
    }
}

вы можете попробовать такой шаблон.

UPDATE: Обновлен код, чтобы отразить комментарии к улучшению.