创建单例来访问统一容器或通过应用程序传递它是否更好?

时间:2010-03-05 12:01:19

标签: c# unity-container ioc-container

我正在使用IoC框架,我选择使用Unity。我还没有完全理解的一件事是如何更深入地解析应用程序中的对象。我怀疑我当时没有灯泡可以说清楚。

所以我尝试在psuedo'ish代码中执行以下操作

void Workflow(IUnityContatiner contatiner, XPathNavigator someXml)
{
   testSuiteParser = container.Resolve<ITestSuiteParser>
   TestSuite testSuite = testSuiteParser.Parse(SomeXml) 
   // Do some mind blowing stuff here
}

所以testSuiteParser.Parse执行以下操作

TestSuite Parse(XPathNavigator someXml)
{
    TestStuite testSuite = ??? // I want to get this from my Unity Container
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml)

    foreach (XPathNavigator blah in aListOfNodes)
    {
        //EDIT I want to get this from my Unity Container
        TestCase testCase = new TestCase() 
        testSuite.TestCase.Add(testCase);
    } 
}

我可以看到三个选项:

  1. 创建一个Singleton来存储我可以在任何地方访问的Unity容器。我真的不喜欢这种方法。添加这样的依赖项来使用依赖注入框架似乎有点奇怪。
  2. 将IUnityContainer传递给我的TestSuiteParser类及其中的每个子类(假设它是n级深度或实际上大约3级深度)。在任何地方传递IUnityContainer只是看起来很奇怪。我可能只需要克服这一点。
  3. 以正确的方式让灯泡瞬间使用Unity。希望有人可以帮助轻弹开关。
  4. [编辑] 我不清楚的一件事是我想为foreach语句的每次迭代创建一个新的测试用例实例。上面的示例需要解析测试套件配置并填充测试用例对象的集合

4 个答案:

答案 0 :(得分:51)

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

在您的示例中,您似乎需要依赖项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子句的组合如何保护类的不变量,确保依赖可用于任何成功创建的TestSuiteParser实例。

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

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

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

(但是,我怀疑可能涉及多个TestCase,在这种情况下,您可能需要inject an Abstract Factory而不是单个TestCase。)

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

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

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

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

创建一个Singleton容器或传递容器只是Service Locator anti-pattern的两种变体,所以我不建议这样做。

答案 1 :(得分:12)

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

我在这里阅读了答案,并没有真正理解正在解释的内容。最后帮助我“得到它”的是这篇文章:

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

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

“99%的代码库应该不了解您的IoC容器。它只是使用容器的根类或引导程序,即使这样,一个解析调用通常只需要构建你的依赖图并启动应用程序或请求。“

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

希望这能帮助那些和我一样迷茫的人! :)

答案 2 :(得分:4)

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

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容器的共享接口,因此您不依赖于特定容器。

答案 3 :(得分:0)

如果只有一个可以传递服务构造函数的“ServiceLocator”,但不知何故设法“声明”它所注入的类的预期依赖关系(即不隐藏依赖关系)......这样,对服务定位器模式的所有(?)异议都可以得到休息。

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的相同实现,无论在注入的类的构造函数中找到泛型声明。