Есть ли шаблон для инициализации объектов, созданных через DI-контейнер

int a = 0;
int b = 0;
145
задан Blorgbeard 29 April 2016 в 02:08
поделиться

4 ответа

Любое место, где требуется значение времени выполнения для построения определённой зависимости, Abstract Factory является решением.

Имея методы инициализации на интерфейсах, пахнет Leaky Abstraction.

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

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

public interface IMyIntf
{
    string RunTimeParam { get; }
}

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

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

Теперь Вы можете создать конкретную реализацию IMyIntfactory, которая создает конкретные экземпляры 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. Никаких методов вонючей инициализации не требуется.

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

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

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

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

.
270
ответ дан 23 November 2019 в 22:48
поделиться

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

Я разделил 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() все еще может вызываться несколько раз, но используя биты парадигмы Service Locator мы можем довольно хорошо обернуть ее так, что IMyIntfSetter является почти внутренним интерфейсом, отличным от IMyIntf.

.
1
ответ дан 23 November 2019 в 22:48
поделиться

Я не могу ответить специфической терминологией Unity, но похоже, что вы только учитесь инъекции зависимости. Если это так, то я настоятельно рекомендую вам прочитать краткое, ясное и информативное руководство пользователя для Ninject.

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

Просмотр также подробно описывает, как аннотировать методы, свойства и даже параметры, используя атрибуты, чтобы отличить их во время выполнения.

Даже если вы не используете Ninject, прохождение даст вам концепции и терминологию функциональности, которая соответствует вашей цели, и вы сможете сопоставить эти знания с Unity или другими структурами ИР (или убедить вас попробовать Ninject)

.
1
ответ дан 23 November 2019 в 22:48
поделиться

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

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

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

.
14
ответ дан 23 November 2019 в 22:48
поделиться