使用DI

时间:2018-10-20 13:58:21

标签: .net dependency-injection inversion-of-control ioc-container compositionroot

我刚刚在PluralSight上观看了一个依赖注入课程,该课程为我清除了几件事。但是,尽管事实上它跨越了几层,但是也没有任何信息说明当您只需要在编码中更新对象时的确切方式。

这是我正在努力解决的难题的最后一部分。我了解Compisition根及其大致用法。我知道要避免ServiceLocator并避免通过在代码库的几乎每个类中将其定义为ctor参数来将IoC容器传递到代码的所有级别(在我工作的公司中我俩人并肩看到过)对于)。

让我们考虑下面的两个示例,我认为不可能将所有内容都交给我们。

我可以注入依赖项,但我可能无法注入所有内容,毕竟它不称为对象注入,因此问题在于如何在循环内动态创建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;
    }
}

现在在某些“动态性较差”的情况下,问题在于某些IO类既没有接口也没有抽象基类可用,这意味着它们很难在测试代码中处理,而且我也不会甚至知道如何使用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 :(得分:1)

DependencyInjection是用于组织复杂的多态应用程序的强大工具。我一直在寻找使用它的机会-但它对于处理应用程序的每个细节都不理想。

尽管它可能只是出于示例目的,但我一般不会考虑使用DI来消除对诸如System.Process之类的标准系统服务的依赖,除非您有充分的理由希望将其替换为自定义Process类。这可能是过度使用依赖注入的情况。

也就是说,当我需要在引导过程之后很好地动态构造类型的实例时,我发现工厂非常有用。为了获得最大的灵活性,请不要依赖单个工厂实例。相反,应提供独立注册不同类型工厂的功能。

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知道他需要做的不同类型的事情-依赖关系是显式的。这使我可以为不同类型的IDoe提供不同(或相同)的工厂类。

如果您不知道最终将要使用的特定实现类型,则需要注入IFactory实例数组,并使用其FactoryType属性将其注册到Dictionary中以供以后查找。

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

答案 1 :(得分:0)

您可以针对工厂中的第一个问题进行操作,如OP中所述,尽管问题是您是否应该这样做。从OP中,尚不清楚IDoees是什么,但通常,作为方法参数传递或作为返回值返回的对象往往是newables rather than injectables。问题是,是否应该注入工厂。如OP中所示,将new向上Doer向上移动可能更合适。

当涉及到Process之类的东西时,尽管您可以从具体类中提取接口,然后创建一个Adapter,但这通常不是最佳选择依赖注入的使用方式。

根据Dependency Inversion Principle抽象不应依赖细节。详细信息应取决于抽象。通常,这意味着客户端应定义所需的抽象,然后实现应与该抽象匹配。

因此,如果客户端必须运行某个进程而不是将其称为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 API来实现Process

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

如果该操作长时间运行,则可以从Task返回Encode,而不是void;如果您需要取消任务,请将CancellationToken作为方法参数传递给Encode方法;等等。围绕客户需求而不是实现细节建模依赖关系。