Как создавать новые объекты при использовании DI - PullRequest
0 голосов
/ 20 октября 2018

Я просто смотрел курс внедрения зависимостей на PluralSight, который кое-что прояснил для меня.Но, несмотря на то, что он охватывает несколько слоев, также не было никакой информации о том, что именно вам нужно делать, когда вам нужно просто создать новые объекты в вашем коде .

.это последняя часть головоломки, которую я изо всех сил пытаюсь понять.Я знаю о Compisition Root и примерно, как его использовать.Я знаю, что нужно избегать ServiceLocator и не передавать контейнер IoC через все уровни кода, определяя его как параметр ctor практически в каждом классе в кодовой базе (что я и видел рядом в компании, в которой работалза).

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

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

public class MyClass
{
    private IVerifyer _verifyer;
    public MyClass(IVerifyer verifyer)
    {
        _verifyer = verifyer;
    }

    public IList<Doer> PrepareDoingSomething(IList<IDoees> doees)
    {
        var doers = new List<Doer>();
        foreach (var doee in doees)
        {
            if (!Verifyer.Verify(doee)) throw new Exception("Blablabla...");
            doers.Add(new Doer(doee));
        }
        return doers;
    }
}

Теперь в некоторых «менее динамичных» случаях проблема заключается в том, что для некоторых классов ввода-вывода нетинтерфейс или доступный абстрактный базовый класс, что означает, что с ними сложно разобраться в тестовом коде, а также я даже не знаю, как обрабатывать их с помощью контейнера IoC, такого как класс Process:

public class Processor
{
    public Process ProcessSomething(IProcessee processee)
    {
        // do some pre-processing stuff here
        // static Start() returns a new Process instance
        return Process.Start("C:\MyApp.exe", $"-option {processee.Option1}");
    }
}

То, что я сделал до сих пор, - это ввел в обоих случаях абстрактный класс фабрики, который я могу внедрить, и это дает мне то, что мне нужно, когда это необходимо (т.е. динамически в цикле).У меня есть реализация для производственного использования.В тестовом коде я могу либо реализовать его, либо просто смоделировать (так как он абстрактный).Для класса Process я ввел прокси-класс с интерфейсом (I) ProcessProxy, который проходит через вызовы к реальному классу Process.Я также могу легко посмеяться над этим, если мне нужно.Затем эти два примера превращаются в то, что я перечислил ниже.

Мой вопрос заключается в том, является ли правильный путь в этих двух случаях (что является моей главной заботой)?Я знаю, что я мог бы вызвать сомнительные ответы, но я просто пытаюсь выяснить, является ли это рекомендуемым способом в духе чистой и прямой текстовой книги, подобной реализации DependencyInjection и CompositionRoot.Если это не самый предпочтительный способ, то что это?

Пример цикла после рефакторинга и DI включен:

public class MyClass
{
    private IVerifyer _verifyer;
    private AbstractDoerFactory _doerFactory;
    public MyClass(IVerifyer verifyer, AbstractDoerFactory doerFactory)
    {
        _verifyer = verifyer;
        _doerFactory = doerFactory;
    }

    public IList<Doer> PrepareDoingSomething(IList<IDoees> doees)
    {
        var doers = new List<Doer>();
        foreach (var doee in doees)
        {
            if (!_verifyer.Verify(doee)) throw new Exception("Blablabla...");
            doers.Add(_doerFactory.GetNewDoer(doee));
        }
        return doers;
    }
}

Пример процесса после рефакторингаи DI включен:

public interface IProcessProxy : IDisposable
{
    TextReader StandardOutput { get; }
    TextReader StandardError { get; }
    int ExitCode { get; }
    Start(string fileName, string arguments);
    void Kill();
}

public class Processor
{
    private AbstractProcessProxyFactory _processProxyFactory;
    public Processor(AbstractProcessProxyFactory processProxyFactory)
    {
        _processProxyFactory = processProxyFactory;
    }

    public IProcessProxy ProcessSomething(IProcessee processee)
    {
        // do some pre-processing stuff here
        var processProxy = _processProxyFactory.GetProxyFactory();
        return processProxy.Start("C:\MyApp.exe", $"-option {processee.Option1}");
    }
}

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

Вы можете обратиться к первому вопросу с фабрикой, как указано в ОП, хотя вопрос заключается в том, следует ли это делать.Из OP не ясно, что такое IDoees, но обычно объекты, которые передаются в качестве аргументов метода или возвращаются как возвращаемые значения, имеют тенденцию быть newables , а не injectables .Вопрос в том, стоит ли вам даже вводить фабрику.Может быть более уместно просто new up Doer, как также показано в ОП.

Когда речь идет о чем-то вроде Process, тогда как вы можете технически извлечьинтерфейс из конкретного класса, а затем создать Adapter , это, как правило, не лучший способ использовать Dependency Injection.

В соответствии с Принципом обращения зависимостей , абстракции не должны зависеть от деталей;детали должны зависеть от абстракций. Обычно это означает, что клиенты должны определять нужную им абстракцию, а затем реализации должны соответствовать этой абстракции.

Таким образом, если клиент должен запустить процесс, а неНазовите это RunProcess, назовите его в соответствии с проблемой, к которой оно относится.Например, если вы хотите запустить программу с именем encode.exe, например, для кодирования некоторых аудиофайлов, смоделируйте зависимость в соответствии с этой целью:

public class Foo
{
    private readonly IEncoder encoder;

    public Foo(IEncoder encoder)
    {
        this.encoder = encoder;
    }

    public Bar DoSomething(Baz baz)
    {
        // Maybe do something else first...

        this.encoder.Encode(/*...*/);

        // Maybe do something else after...
    }
}

Затем вы можете реализовать IEncoder, используя Process API, если вам нужно:

public class Encoder : IEncoder
{
    public void Encode(/*...*/)
    {
        // Start encode.exe here...
    }
}

Если операция длительная, вы можете вернуть Task из Encode вместо void;если вам нужно отменить задачу, передайте CancellationToken в качестве аргумента метода методу Encode;и так далее.Смоделируйте зависимость вокруг требований клиента (-ов), а не деталей реализации.

0 голосов
/ 21 октября 2018

DependencyInjection - невероятно мощный инструмент для организации сложных полиморфных приложений.Я всегда ищу возможности его использовать, но он не идеален для обработки каждой детали вашего приложения.

Хотя это могло быть только в качестве примера, я бы не стал использовать DI для устранения зависимости от стандартного системного сервиса, такого как System.Process, если у вас не было очень веской причины для его замены.с пользовательским классом Process.Это может быть случай чрезмерного использования Dependency Injection.

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

public interface IFactory
{ 
    Type FactoryType;
    object CreateInstance(); 
}

namespace Generic {
    public interface IFactory<T> : IFactory
    {
        new T CreateInstance();
    }
}

public class DoThisFactory : IFactory<DoThis> { ... }
public class DoThatFactory : IFactory<DoThat> { ... }


public class MyClass
{
    // use property injection
    public DoThisFactory DoThisFactory { get; set; }       
    public DoThatFactory DoThatFactory { get; set; }

    private DoThis _doThis = null;
    private DoThat _doThat = null;

    public DoThis DoThis
    {
        get {
            if(_doThis == null) _doThis = DoThisFactory.CreateInstance();
            return _doThis;
        }
    }

    public DoThat DoThat
    {
        get {
            if(_doThat == null) _doThat = DoThatFactory.CreateInstance();
            return _doThat;
        }
    }
}

В приведенном выше примере MyClass знает, что ему нужно делать, - зависимости явные.Это позволяет мне предоставлять разные (или одинаковые) фабричные классы для разных типов IDoees.

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

public class MyClass
{
    private Dictionary<Type, IFactory> _registerations = new Dictionary<Type, IFactory>();

    public MyClass(IFactory[] factories)
    {
        for(int i = 0; i < factories.Count -1; i++)
        {
            _registrations.Add(factories(i).FactoryType, factories);
        }
    }

    public IEnumeruable<IDoer> GetDoers(Type[] types)
    {
        List<IDoer> doers = new List<IDoer>();
        for(int i = 0; i < types.Count - 1; i++)
        {
            doers.Add(_registerations(types(i)).CreateInstance());
        }
        return doers;
    }
}
...