库中控制反转的最佳实践?

Best practices for Inversion of Control in libraries?

我的任务是建立代码模式以用于我们将要构建的新应用程序。早期决定使用 Prism 和 MEF 构建我们的应用程序和库,目的是简化跨应用程序的测试和功能重用。

随着我们的代码库的增长,我 运行 遇到了一个问题。 假设我们有一些基础库需要访问某个系统——假设是一个用户管理系统:

public interface IUserManagementSystem
{
    IUser AuthenticateUser(string username, string password);
}
// ...
public class SomeClass
{
   [ImportingConstructor]
   public SomeClass(IUserManagementSystem userSystem){ }
}

我们现在可以使用 ServiceLocator 创建 SomeClass 类型的对象,但前提是已注册 IUserManagementSystem 的实现。无法知道(在编译时)创建是成功还是失败,需要什么实现或其他关键信息。

如果我们使用库中的ServiceLocator,这个问题会变得更加复杂。

查找现在隐藏的依赖项已成为比遗留应用程序中的硬编码依赖项更大的问题。 IoC 和依赖注入模式并不新鲜,一旦我们将这项工作从编译器中拿走,我们应该如何管理依赖关系?我们是否应该完全避免在库代码中使用 ServiceLocator(或其他 IoC 容器)?

编辑:我特别想知道如何处理横切关注点(例如日志记录、通信和配置)。我看过一些关于构建 CCC 项目的建议(大概是大量使用单例)。在其他情况下 (Is the dependency injection a cross-cutting concern?),建议在整个库中使用 DI 框架,从而回到跟踪依赖关系的原始问题。

Dependency Inject (DI) "friendly" library 有点相关,因为它解释了如何以可以使用或不使用 DI 框架的方式构建代码,但它没有说明使用 DI 是否有意义 库中或如何确定给定库所需的依赖项。那里标记的答案提供了关于与 DI 框架交互的可靠架构建议,但它没有解决我的问题。

您不应在核心库中使用 ServiceLocator 或任何其他特定于 DI 框架的部分。这将您的代码耦合到特定的 DI 框架,并使得任何使用您的库的人都必须将该 DI 框架添加为他们产品的依赖项。相反,使用构造函数注入和各种技术 mentioned by Mark Seeman in his excellent answer here

然后,为了帮助您的库的用户更轻松地启动,您可以提供单独的 DI 容器特定库,其中包含一些基本实用程序 classes 来处理您正在使用的大多数标准 DI 绑定期望人们使用,所以在很多情况下他们可以从一行代码开始。

在 运行 之前缺少依赖项的问题是使用 DI 框架时的常见问题。解决它的唯一真正方法是使用 "poor-man's DI",其中您有一个 class 实际上定义了每种类型的对象是如何构造的,因此如果引入了您的依赖项,则会出现编译时错误没处理过

但是,您通常可以通过尽早检查 运行 时间来缓解问题。例如,SimpleInjector 有一个 Validate() 方法,您可以在设置所有绑定后调用该方法,如果它知道它不知道如何为任何类型构造依赖项,它将抛出异常已注册到它。我还喜欢在我的测试套件中添加一个简单的集成测试,它设置所有绑定,然后尝试在我的应用程序中构建所有顶级类型(例如 Web API 控制器),如果可以则失败不要这样做。