1

我刚刚在 PluralSight 上观看了一个依赖注入课程,它为我解决了一些问题。但是,尽管它跨越了几层,但也没有关于当你只需要在你的 codede 中更新对象时究竟要走什么路的信息。

这是我努力掌握的最后一块拼图。我知道 Compisition Root 以及大致如何使用它。我知道要避免使用 ServiceLocator 并通过将 IoC 容器定义为代码库中几乎每个类中的 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}");
    }
}
4

2 回答 2

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 知道他需要做的不同类型的事情——依赖关系是明确的。这允许我为不同类型的 IDoees 提供不同(或相同)的工厂类。

如果您不知道最终将使用的具体实现类型,那么您将需要注入一个 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;
    }
}
于 2018-10-21T12:08:01.510 回答
0

如 OP 中所述,您可以解决工厂的第一个问题,尽管问题是您是否应该这样做。从 OP 来看,尚不清楚什么IDoees是,但通常,作为方法参数传递或作为返回值返回的对象往往是newables而不是injectionables。那么,问题是你是否应该注入一个工厂。new简单的 up可能更合适Doer,如 OP 中所示。

当涉及到类似的东西Process时,虽然您可以从技术上从具体类中提取接口,然后制作一个Adapter,但这通常不是使用依赖注入的最佳方式。

根据依赖倒置原则抽象不应该依赖于细节;细节应该取决于抽象。这通常意味着客户应该定义他们需要的抽象,然后实现应该匹配该抽象。

因此,如果客户端必须运行一个进程,而不是调用它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...
    }
}

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

于 2018-11-01T07:21:09.040 回答