创建一个单例来访问 Unity 容器还是通过应用程序传递它更好?

发布于 2024-08-24 12:48:53 字数 1431 浏览 7 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

想挽留 2024-08-31 12:48:53

DI 的正确方法是使用构造函数注入或其他 DI 模式(但构造函数注入是最常见的)将依赖项注入到使用者中,无论 DI 容器如何

在您的示例中,您似乎需要依赖项 TestSuiteTestCase,因此您的 TestSuiteParser 类应该通过询问来静态宣布它需要这些依赖项通过其(唯一的)构造函数:

public class TestSuiteParser
{
    private readonly TestSuite testSuite;
    private readonly TestCase testCase;

    public TestSuiteParser(TestSuite testSuite, TestCase testCase)
    {
        if(testSuite == null)
        {
            throw new ArgumentNullException(testSuite);
        }
        if(testCase == null)
        {
            throw new ArgumentNullException(testCase);
        }

        this.testSuite = testSuite;
        this.testCase = testCase;
    }

    // ...
}

注意 readonly 关键字和 Guard Clause 的组合如何保护类的不变量,确保依赖项可用于任何对象成功创建 TestSuiteParser 实例。

您现在可以像这样实现 Parse 方法:(

public TestSuite Parse(XPathNavigator someXml) 
{ 
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml) 

    foreach (XPathNavigator blah in aListOfNodes) 
    { 
        this.testSuite.TestCase.Add(this.testCase); 
    }  
} 

但是,我怀疑可能涉及多个 TestCase,在这种情况下您可能需要 注入一个抽象工厂而不是单个测试用例。)

从您的 Composition Root,您可以配置Unity(或任何其他容器):

container.RegisterType<TestSuite, ConcreteTestSuite>();
container.RegisterType<TestCase, ConcreteTestCase>();
container.RegisterType<TestSuiteParser>();

var parser = container.Resolve<TestSuiteParser>();

当容器解析 TestSuiteParser 时,它理解构造函数注入模式,因此它自动连接具有所有所需依赖项的实例。

创建单例容器或传递容器只是服务定位器反的两种变体模式,所以我不推荐这样做。

The correct approach to DI is to use Constructor Injection or another DI pattern (but Constructor Injection is the most common) to inject the dependencies into the consumer, irrespective of DI Container.

In your example, it looks like you require the dependencies TestSuite and TestCase, so your TestSuiteParser class should statically announce that it requires these dependencies by asking for them through its (only) constructor:

public class TestSuiteParser
{
    private readonly TestSuite testSuite;
    private readonly TestCase testCase;

    public TestSuiteParser(TestSuite testSuite, TestCase testCase)
    {
        if(testSuite == null)
        {
            throw new ArgumentNullException(testSuite);
        }
        if(testCase == null)
        {
            throw new ArgumentNullException(testCase);
        }

        this.testSuite = testSuite;
        this.testCase = testCase;
    }

    // ...
}

Notice how the combination of the readonly keyword and the Guard Clause protects the class' invariants, ensuring that the dependencies will be available to any successfully created instance of TestSuiteParser.

You can now implement the Parse method like this:

public TestSuite Parse(XPathNavigator someXml) 
{ 
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml) 

    foreach (XPathNavigator blah in aListOfNodes) 
    { 
        this.testSuite.TestCase.Add(this.testCase); 
    }  
} 

(however, I suspect that there may be more than one TestCase involved, in which case you may want to inject an Abstract Factory instead of a single TestCase.)

From your Composition Root, you can configure Unity (or any other container):

container.RegisterType<TestSuite, ConcreteTestSuite>();
container.RegisterType<TestCase, ConcreteTestCase>();
container.RegisterType<TestSuiteParser>();

var parser = container.Resolve<TestSuiteParser>();

When the container resolves TestSuiteParser, it understands the Constructor Injection pattern, so it Auto-Wires the instance with all its required dependencies.

Creating a Singleton container or passing the container around are just two variations of the Service Locator anti-pattern, so I wouldn't recommend that.

手长情犹 2024-08-31 12:48:53

我是依赖注入的新手,我也有这个问题。我一直在努力思考 DI,主要是因为我专注于将 DI 应用于我正在处理的一个类,一旦我将依赖项添加到构造函数中,我立即尝试找到某种方法来获得统一容器到需要实例化此类的位置,以便我可以调用该类的 Resolve 方法。因此,我正在考虑使统一容器作为静态全局可用或将其包装在单例类中。

我在这里阅读了答案,但并没有真正理解所解释的内容。最终帮助我“明白”的是这篇文章:

http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

这特别是段落是“灯泡”时刻:

“99% 的代码库应该不了解 IoC 容器。只有根类或引导程序使用该容器,即使如此,单个解析调用是构建依赖关系图并启动应用程序或请求通常所需的全部内容。”

这篇文章帮助我理解,我实际上不能访问整个应用程序中的统一容器,而只能访问应用程序的根目录。因此,我必须重复应用 DI 原则,一直回到应用程序的根类。

希望这可以帮助其他像我一样困惑的人! :)

I am new to Dependency Injection and I also had this question. I was struggling to get my mind around DI, mainly because I was focusing on applying DI to just the one class that I was working on and once I had added the dependencies to the constructor, I immediately tried to find some way to get the unity container to the places where this class needed to be instantiated so that I could call the Resolve method on the class. As a result I was thinking along the lines of making the unity container globally available as a static or wrapping it in a singleton class.

I read the answers here and did not really understand what was being explained. What finally helped me "get it" was this article:

http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

And this paragraph in particular was the "light bulb" moment:

"99% of your code base should have no knowledge of your IoC container. It is only the root class or bootstrapper that uses the container and even then, a single resolve call is all that is typically necessary to build your dependency graph and start the application or request."

This article helped me understand that I actually must not access the unity container all over the application, but only at the root of the application. So I must apply the DI principle repeatedly all the way back to the root class of the application.

Hope this helps others who are as confused as I was! :)

为你拒绝所有暧昧 2024-08-31 12:48:53

您实际上并不需要在应用程序的很多地方直接使用容器。您应该在构造函数中获取所有依赖项,而不是从您的方法中访问它们。您的示例可能是这样的:

public class TestSuiteParser : ITestSuiteParser {
    private TestSuite testSuite;

    public TestSuitParser(TestSuit testSuite) {
        this.testSuite = testSuite;
    }

    TestSuite Parse(XPathNavigator someXml)
    {
        List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml)

        foreach (XPathNavigator blah in aListOfNodes)
        {
            //I don't understand what you are trying to do here?
            TestCase testCase = ??? // I want to get this from my Unity Container
            testSuite.TestCase.Add(testCase);
        } 
    }
}

然后您在整个应用程序中以相同的方式执行此操作。当然,在某些时候你必须解决一些问题。例如,在 asp.net mvc 中,这个位置位于控制器工厂中。那是创建控制器的工厂。在此工厂中,您将使用容器来解析控制器的参数。但这只是整个应用程序中的一个地方(当您做更高级的事情时可能还有更多地方)。

还有一个不错的项目,名为 CommonServiceLocator。这是一个为所有流行的 ioc 容器提供共享接口的项目,这样您就不会依赖于特定的容器。

You should not really need to use your container directly in very many places of your application. You should take all your dependencies in the constructor and not reach them from your methods. You example could be something like this:

public class TestSuiteParser : ITestSuiteParser {
    private TestSuite testSuite;

    public TestSuitParser(TestSuit testSuite) {
        this.testSuite = testSuite;
    }

    TestSuite Parse(XPathNavigator someXml)
    {
        List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml)

        foreach (XPathNavigator blah in aListOfNodes)
        {
            //I don't understand what you are trying to do here?
            TestCase testCase = ??? // I want to get this from my Unity Container
            testSuite.TestCase.Add(testCase);
        } 
    }
}

And then you do it the same way all over the application. You will, of course, at some point have to resolve something. In asp.net mvc for example this place is in the controller factory. That is the factory that creates the controller. In this factory you will use your container to resolve the parameters for your controller. But this is only one place in the whole application (probably some more places when you do more advanced stuff).

There is also a nice project called CommonServiceLocator. This is a project that has a shared interface for all the popular ioc containers so that you don't have a dependency on a specific container.

呆头 2024-08-31 12:48:53

如果只有一个人可以拥有一个在服务构造函数中传递的“ServiceLocator”,但以某种方式设法“声明”它所注入的类的预期依赖项(即不隐藏依赖项)...这样, all(? )可以平息对服务定位器模式的反对。

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<Dependency1, Dependency2, Dependency3> locator)
    {
        //keep the resolver for later use
    }
}

可悲的是,上面的内容显然只存在于我的梦想中,因为 c# 禁止可变泛型参数(仍然),因此每次需要额外的泛型参数时手动添加一个新的泛型接口将是笨拙的。

另一方面,如果尽管 c# 的限制可以通过以下方式实现上述内容...

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<TArg<Dependency1, TArg<Dependency2, TArg<Dependency3>>> locator)
    {
        //keep the resolver for later use
    }
}

这样,只需进行额外的输入即可实现相同的效果。
我还不确定的是,考虑到 TArg 类的正确设计(我假设将采用巧妙的继承来允许 TArg 通用参数的无限嵌套) ,DI 容器将能够正确解析 IServiceResolver。最终的想法是简单地传递 IServiceResolver 的相同实现,无论在注入的类的构造函数中找到什么通用声明。

If only one could have a "ServiceLocator" that gets passed around service constructors, but somehow manages to "Declare" the intended dependencies of the class it is being injected into (i.e not hide the dependencies)...that way, all(?) objections to the service locator pattern can be put to rest.

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<Dependency1, Dependency2, Dependency3> locator)
    {
        //keep the resolver for later use
    }
}

Sadly, the above obviously will only ever exist in my dreams, as c# forbids variable generic parameters (still), so manually adding a new generic Interface each time one needs an additional generic parameter, would be unwieldy.

If on the other hand, the above could be achieved despite the limitation of c# in the following way...

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<TArg<Dependency1, TArg<Dependency2, TArg<Dependency3>>> locator)
    {
        //keep the resolver for later use
    }
}

This way, one only needs to do extra typing to achieve the same thing.
What i am not sure of yet is if, given the proper design of the TArg class (i assume clever inheritance will be employed to allow for infinite nesting of TArg Generic parameters), DI containers will be able to resolve the IServiceResolver properly. The idea, ultimately, is to simply pass around the very same implementation of the IServiceResolver no matter the generic declaration found in the constructor of class being injected into.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文