代码之家  ›  专栏  ›  技术社区  ›  btlog Stefan Wick MSFT

创建一个singleton来访问Unity容器还是通过应用程序传递它更好?[关闭]

  •  48
  • btlog Stefan Wick MSFT  · 技术社区  · 14 年前

    我正在尝试使用IOC框架,我选择使用Unity。我仍然不完全理解的一件事是如何在应用程序中更深入地解析对象。我怀疑我只是暂时没有打开灯泡,这会让我明白。

    所以我尝试在psuedo的代码中做如下的事情

    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级深)。到处都能看到一个漂亮的容器。我可能需要克服这个问题。
    3. 用正确的方法使灯泡时刻统一。希望有人能帮忙拨动开关。

    [编辑] 我不清楚的一件事是,我想为for each语句的每次迭代创建一个新的测试用例实例。上面的示例需要解析一个测试套件配置并填充一个测试用例对象集合。

    4 回复  |  直到 8 年前
        1
  •  51
  •   Community Stefan Steinegger    7 年前

    使用DI的正确方法是 构造器注入 或者另一个DI模式(但构造函数注入是最常见的)将依赖项注入到使用者中, 不考虑DI容器 .

    在您的示例中,看起来您需要依赖项 TestSuite TestCase ,因此您的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); 
        }  
    } 
    

    (不过,我怀疑可能涉及多个测试用例,在这种情况下,您可能希望 inject an Abstract Factory 而不是单个测试用例。)

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

    container.RegisterType<TestSuite, ConcreteTestSuite>();
    container.RegisterType<TestCase, ConcreteTestCase>();
    container.RegisterType<TestSuiteParser>();
    
    var parser = container.Resolve<TestSuiteParser>();
    

    当容器解析testsuiteparser时,它了解构造函数注入模式,因此 汽车线材 具有所有必需依赖项的实例。

    创建一个单独的容器或传递容器只是 Service Locator anti-pattern 所以我不推荐。

        2
  •  12
  •   BruceHill    12 年前

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

    我读了这里的答案,并没有真正理解被解释的是什么。最终帮助我“得到它”的是这篇文章:

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

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

    “99%的代码库应该不知道您的IOC容器。只有根类或引导程序使用容器,即使这样,一个解析调用通常都是构建依赖关系图和启动应用程序或请求所必需的。”

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

    希望这能帮助像我一样困惑的人!:)

        3
  •  4
  •   Mattias Jakobsson    14 年前

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

    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容器提供了一个共享接口,这样您就不会依赖于特定的容器。

        4
  •  0
  •   Dantte    8 年前

    如果只有一个可以有一个“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 类(我假设聪明的继承将被用于允许无限嵌套 塔格 一般参数),DI容器将能够解决 IServiceResolver 适当地。最终的想法是简单地传递 IServiceResolver 无论在要注入的类的构造函数中找到什么泛型声明。