一尘不染

依赖注入(DI)“友好”库

c#

我正在考虑C#库的设计,该库将具有几个不同的高级功能。当然,这些高级功能将尽可能使用SOLID类设计原则来实现。这样,可能会有打算供消费者定期直接使用的类以及作为那些较常见的“最终用户”类的依赖的“支持类”。

问题是,设计库的最佳方法是什么:

  • DI不可知-尽管为一个或两个常见的DI库(StructureMap,Ninject等)添加基本的“支持”似乎是合理的,但我希望消费者能够将库与任何DI框架一起使用。
  • 非DI可用-如果库的使用者不使用DI,则该库仍应尽可能易于使用,从而减少用户创建所有这些“不重要”依赖项而要做的工作量。他们想要使用的“真实”类。

我当前的想法是为常见的DI库提供一些“
DI注册模块”(例如StructureMap注册表,Ninject模块)以及非DI的set或Factory类,并包含与这几个工厂的耦合。

有什么想法吗?


阅读 243

收藏
2020-05-19

共1个答案

一尘不染

一旦了解DI是关于 模式和原理 ,而不是技术,这实际上很容易做到。

要以与DI容器无关的方式设计API,请遵循以下一般原则:

编程到接口,而不是实现

该原则实际上是设计模式的引用(虽然来自内存),但它应该始终是您的
真正目标DI只是实现这一目标的一种手段

适用好莱坞原则

用DI的好莱坞原则说: 不要叫DI容器,它会叫你

切勿通过在代码内调用容器直接请求依赖项。通过使用 构造函数注入 隐式地请求它。

使用构造函数注入

当您需要依赖项时,可以通过构造函数 静态地 请求它:

public class Service : IService
{
    private readonly ISomeDependency dep;

    public Service(ISomeDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public ISomeDependency Dependency
    {
        get { return this.dep; }
    }
}

注意Service类如何保证其不变性。创建实例后,由于Guard子句和readonly关键字的组合,因此可以确保依赖项可用。

如果需要短暂的对象,请使用Abstract Factory

使用构造函数注入注入的依赖关系往往是长期存在的,但是有时您需要一个短暂的对象,或者基于仅在运行时已知的值来构建依赖关系。

请参阅以获取更多信息。

仅在最后负责的时刻撰写

使对象解耦,直到最后。通常,您可以等待并在应用程序的入口点连接所有内容。这称为 合成根

此处有更多详细信息:

使用立面简化

如果您觉得由此产生的API对于新手用户来说太复杂了,则可以始终提供一些封装常见依赖项组合的Facade类。

为了提供具有高度可发现性的灵活立面,您可以考虑提供Fluent Builders。像这样:

public class MyFacade
{
    private IMyDependency dep;

    public MyFacade()
    {
        this.dep = new DefaultDependency();
    }

    public MyFacade WithDependency(IMyDependency dependency)
    {
        this.dep = dependency;
        return this;
    }

    public Foo CreateFoo()
    {
        return new Foo(this.dep);
    }
}

这将允许用户通过编写默认Foo

var foo = new MyFacade().CreateFoo();

但是,很可能会发现有可能提供自定义依赖项,您可以编写

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

如果您想象MyFacade类封装了许多不同的依赖项,那么我希望很清楚,它将在提供可扩展性的同时提供适当的默认值。


FWIW,在写了这个答案很长时间之后,我扩展了本文的概念,并撰写了一篇有关DI友好库的较长博客文章,以及一篇关于DI友好框架的随笔文章。

2020-05-19