依赖注入和/或全局单例

时间:2018-12-05 11:50:39

标签: dependency-injection .net-core singleton

我是依赖注入模式的新手。我喜欢这个主意,但努力将其应用于我的案例。我有一个单例对象,我们称其为X,我经常在程序的许多部分,许多不同的类中使用它,有时甚至在调用堆栈的深处。通常,我会将其实现为全局可用的单例。如何在DI模式(特别是.NET Core DI容器)中实现此功能?我知道我需要将DI单独注册到DI容器中,但是如何访问它呢? DI将使用构造函数实例化类,这些构造函数将引用X,这很棒–但是,我需要在调用层次结构中的X深入,在我自己的对象(.NET Core或DI容器一无所知)中,在使用new而不是创建的对象中由DI容器实例化。

我想我的问题是–全局单例模式如何与DI模式对齐/实现/替换/避免使用DI模式?

3 个答案:

答案 0 :(得分:0)

好吧,“ new是胶水”(Link)。这意味着,如果您已经new了一个实例,它将被粘贴到您的实现中。您不能轻易地将其与其他实现交换,例如用于测试的模拟。就像将乐高积木粘在一起。

我想使用适当的依赖项注入(无论是否使用容器/框架),所以您需要以不将组件粘合在一起而是注入它们的方式来构造程序。

然后,每个类基本上都处于层次结构1级。您需要记录器的实例吗?您注入它。您需要一个需要记录器的类的实例吗?您注入它。您想测试您的日志记录机制吗?容易,您只需注入符合您的记录器界面的内容即可登录到列表中,并且在测试结束时,您可以检查列表并查看是否存在所有必需的日志。这是可以自动化的(与使用常规日志记录机制并手动检查日志文件相反)。

这意味着最后,您实际上并没有一个层次结构,因为您刚刚注入的每个类都将注入其依赖项,而容器/框架或您的控制代码将决定实例化顺序的含义对象。


就设计模式而言,请允许我观察一下:即使现在,您也不需要 一个单例。现在在您的程序中,如果您有一个简单的全局变量,它将可以正常工作。但是我想您读过全局变量是“错误的”。设计模式是“好的”。而且,由于您需要一个全局变量,而单例传递一个全局变量,那么当可以使用“好”字时,为什么要使用“坏”字呢?嗯,问题是,即使是单例,全局变量也是错误的。这是模式的后退,您必须吞下一只蟾蜍才能使单例逻辑起作用。在您的情况下,您不需要单例逻辑,但是您喜欢蟾蜍的味道。因此,您创建了一个单例。不要使用设计模式来做到这一点。仔细阅读它们,并确保将它们用于预期目的,而不是因为您喜欢它们的副作用或使用设计模式感觉很好。

答案 1 :(得分:0)

只是一个想法,也许我需要你的想法:

public static class DependencyResolver
{
    public static Func<IServiceProvider> GetServiceProvider;
}

然后在启动中:

public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
{
    DependencyResolver.GetServiceProvider = () => { return serviceProvider; };
}

现在在任何契约类中:

DependencyResolver.GetServiceProvider().GetService<IService>();

答案 2 :(得分:0)

这里是一个简单的示例,说明了如何在没有单例情况下工作。 本示例假定您的项目是通过以下方式构建的:

  • 入口是主要的
  • main创建类GuiCreator的实例,然后调用方法createAndRunGUI()
  • 其他所有方法均由该方法处理

所以您的简化代码如下:

// main
// ... (boilerplate)
container = new Container();
gui = new GuiCreator(container.getDatabase(), container.getLogger(), container.getOtherDependency());
gui.createAndRunGUI();
// ... (boilerplate)

// GuiCreator
public class GuiCreator {
    private IDatabase db;
    private ILogger log;
    private IOtherDependency other;
    
    public GuiCreator(IDatabase newdb, ILogger newlog, IOtherDependency newother) {
        db = newdb;
        log = newlog;
        other = newother;
    }
    
    public void createAndRunGUI() {
        // do stuff
    }
}

您可以在Container类中实际定义将使用的实现,而GuiCreator构造函数则将接口作为参数。现在,假设您选择的ILogger实现本身具有依赖关系,该依赖关系由其构造函数作为参数的接口定义。容器知道了这一点,并通过将Logger实例化为new LoggerImplementation(getLoggerDependency());来解决它。整个依赖链都这样。

所以本质上:

  • 所有类都将它们依赖的接口实例保留为成员。
  • 这些成员在各自的构造函数中设置。
  • 因此,当实例化第一个对象时,将解决整个依赖链。请注意,这里可能/应该涉及一些延迟加载。
  • 访问容器的方法以创建实例的唯一位置是在容器的main和内部:
    • 在main中使用的任何类都从main的容器实例接收其依赖项。
    • 任何未在main中使用但仅用作依赖项的类都由容器实例化,并从那里接收其依赖项。
    • 在main中使用的类之下的任何类都不会在main中或间接用作依赖项,显然不会实例化。
  • 因此,实际上没有任何类需要对容器的引用。实际上,您的项目中甚至没有班级甚至不需要知道容器。他们只知道他们个人需要哪些接口。

容器可以由某些第三方库/框架提供,也可以自己编写。通常,它将使用一些配置文件来确定哪些实现实际上应该用于各种接口。第三方容器通常会执行“自动装配”实现的注释所支持的某种代码分析,因此,如果您使用现成的工具,请确保已阅读该部分的工作原理,因为它通常会使您的生活更轻松道路。