如何在不强迫用户使用库的IOC容器的情况下编写库

时间:2012-03-24 14:11:30

标签: c# dependency-injection inversion-of-control ioc-container

简短的问题是: 鉴于库保证使用特定的IOC容器作为其内部,当应用程序使用该库时,给定应用保证使用IOC容器连接其依赖关系,如果两个容器是不同的,它们怎么能很好地一起玩?

场景是,应用程序定义了依赖于库中类型的类。因此,当应用程序容器尝试构建此类时,它需要知道如何解析库中的类型。

这是一个冗长的问题:

这个问题似乎确实在SO之前以不同的形式和形式提出过,但我似乎无法找到我需要的答案,因此我将使用一个假设的_over_simplified_具体示例。 我们希望编写一个用于日志记录的库,用户可以在其解决方案中将其作为包包含在内,以便开箱即用地获取日志记录功能。 库公开的公共接口是..

public interface ILogger {}

public interface ITarget {}

具体实施

internal class Logger: ILogger { public Logger(ITarget target) {}}

internal class FileTarget : ITarget {}

要求是,如果用户包含我们的包并定义了一个类型为ILogger的类或具有类型为ILogger的ctor参数的类,那么我们的库负责为该接口注入具体实现进入用户定义的类。默认情况下,注入的logger将转到文件系统进行日志记录,因为ITarget实现中注入的ILogger的默认实现是我们库的FileTarget

如果用户决定编写实现ITarget接口的类,那么我们的库将使用它来注入Logger类,而不使用其默认的FileTarget实现。

所以我希望展示的是,他们在这里是双向依赖。

  1. 我们的库依赖于用户的程序集,因为它需要扫描用户的程序集以加载任何扩展点(即ITarget实现),并在任何默认实现之前将它们注入到自己的对象中。

  2. 用户的程序集依赖于库,因为如果用户选择将ILogger接口定义为依赖项,那么该用户对象应该获得对运行时提供的该接口的具体引用我们的图书馆。

  3. 简单的解决方案是,如果用户和我们的库都使用相同的IOC容器,那么问题就解决了。但这是一个强有力的假设。我想做的是

    1. 使用IOC容器和最符合库要求的库,在我的例子中是Ninject。
    2. 在运行时以某种方式为用户提供了一种机制,让用户通过某些API调用我的库,这将确保启动Ninject,并扫描用户的程序集,并将考虑到所有扩展点的所有内容连接起来。
    3. 到目前为止这么好,它完全可以实现,但这里有一个棘手的部分。

      • 如果用户也在使用Ninject,那么问题会自动解决,因为Ninject已经知道如何解析生活在我们库中的接口。但是如果用户决定使用他/她选择的IOC容器呢?

      我几乎想在库中使用类似

      的界面定义某种子容器功能

      public interface IDependencyResolvingModule { T Get<T>(); []T GetAll<T>(); }

      并提供一个实现,它使用我们库的容器选择(即Ninect)来解析上面定义的两种方法中请求的类型。

      我希望用户的IOC容器具有一些功能,如果它无法解析依赖关系(即ILogger),它应该挂钩到IDependencyResolvingModule实现并请求依赖。< / p>

      这样我们的库就可以使用它选择的IOC容器,并且用户的代码有办法解决其IOC容器没有任何线索的依赖关系。如果IOC容器有一些如何提供功能来注册执行程序集目录中的程序集中找到的任何IDependencyResolverModules的单例实例以及当它们无法解析类型时,请问任何单例模块?

      但是,除非要求所有其他IOC容器都能容纳,否则如何解决这个问题呢?所以几行中的问题是,当第三方程序集选择使用IOC容器作为其内部时,什么是一个简单的解决方案,这样该库可以简单地为外面的IOC容器提供一种机制来挂钩并解决依赖关系住在图书馆里。

1 个答案:

答案 0 :(得分:3)

我在这里看到几种可能的方法:

  1. 为所有流行的IoC容器编写默认的registrator。它们中的每一个都应放在单独的组件中。然后开发人员可以选择他需要的那个并用它配置他的容器。

  2. 定义您自己的工厂抽象并编写将返回默认记录器的默认实现。让开发人员替换该工厂的实现。例如,使用适配器为他最喜欢的容器。这种方法与容器无关,因为开发人员只能使用默认的工厂实现。但这种方式与自动布线无关。

  3. 第一种方法的懒惰变化。编写关于配置容器以使用默认实现的小手册。然后开发人员可以自己配置容器。

  4. 结合所有以前的解决方案,以满足每个开发人员。 :)

  5. 编辑:添加了两个容器集成的示例

    var allPublicInterfacesFromLibrary = typeof(AnyLibraryType)
        .Assembly.GetTypes()
        .Where(t => t.IsInterface && t.IsPublic);
    
    foreach (var libraryInterface in allPublicInterfacesFromLibrary)
    {
        var local = libraryInterface; //to prevent closure
        applicationContainer.Register(
            Component.For(libraryInterface)
            //delegate resolving
            .UsingFactoryMethod(k => libraryContainer.Resolve(local)) 
            //delegate lifetime management
            .LifestyleTransient() 
        );
    }