É melhor criar um singleton para acessar o container da unidade ou passá-lo pelo aplicativo?

Eu estou mergulhando meu dedo em usando um quadro de IoC e escolhi usar o Unity. Uma das coisas que ainda não entendo completamente é como resolver objects mais profundamente no aplicativo. Eu suspeito que eu não tive a lâmpada no momento que vai deixar claro.

Então, eu estou tentando fazer algo como o seguinte no código psuedo’ish

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

Então o testSuiteParser.Parse faz o seguinte

 TestSuite Parse(XPathNavigator someXml) { TestStuite testSuite = ??? // I want to get this from my Unity Container List 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); } } 

Eu posso ver três opções:

  1. Crie um Singleton para armazenar meu contêiner de unidade que posso acessar em qualquer lugar. Eu realmente não sou fã dessa abordagem. Adicionar uma dependência como essa para usar uma estrutura de injeção de dependência parece um pouco no lado ímpar.
  2. Passe o IUnityContainer para minha class TestSuiteParser e cada filho dele (suponha que ele tenha n níveis de profundidade ou, na realidade, cerca de 3 níveis de profundidade). Passar o IUnityContainer por todos os lugares parece estranho. Eu posso apenas precisar superar isso.
  3. Tenha o momento da lâmpada no caminho certo para usar o Unity. Esperando que alguém possa ajudar a apertar o botão.

[EDIT] Uma das coisas que eu não estava claro é que eu quero criar uma nova instância de caso de teste para cada iteração da instrução foreach. O exemplo acima precisa analisar uma configuração do conjunto de testes e preencher uma coleção de objects de caso de teste

   

    A abordagem correta para DI é usar a Injeção de Construtor ou outro padrão de DI (mas a Injeção de Construtor é a mais comum) para injetar as dependencies no consumidor, independentemente do Contêiner DI .

    No seu exemplo, parece que você precisa das dependencies TestSuite e TestCase , portanto, sua class TestSuiteParser deve estaticamente anunciar que requer essas dependencies, solicitando-as por meio de seu (único) construtor:

     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; } // ... } 

    Observe como a combinação da palavra-chave readonly e da Cláusula de proteção protege as invariantes da class, garantindo que as dependencies estejam disponíveis para qualquer instância criada com sucesso do TestSuiteParser.

    Agora você pode implementar o método Parse assim:

     public TestSuite Parse(XPathNavigator someXml) { List aListOfNodes = DoSomeThingToGetNodes(someXml) foreach (XPathNavigator blah in aListOfNodes) { this.testSuite.TestCase.Add(this.testCase); } } 

    (no entanto, suspeito que possa haver mais de um TestCase envolvido, caso em que você pode querer injetar um Abstract Factory em vez de um único TestCase.)

    De sua raiz de composição , você pode configurar o Unity (ou qualquer outro contêiner):

     container.RegisterType(); container.RegisterType(); container.RegisterType(); var parser = container.Resolve(); 

    Quando o contêiner resolve o TestSuiteParser, ele entende o padrão de Injeção do Construtor, de modo que ele auto-conecta a instância com todas as dependencies necessárias.

    Criar um contêiner Singleton ou passar o contêiner ao redor são apenas duas variações do anti- padrão Service Locator , então eu não recomendaria isso.

    Eu sou novo em Dependency Injection e também tive essa pergunta. Eu estava lutando para me concentrar em DI, principalmente porque eu estava focando em aplicar DI apenas na class em que eu estava trabalhando e uma vez que eu adicionei as dependencies ao construtor, eu imediatamente tentei encontrar alguma maneira de obter a unidade container para os locais onde essa class precisava ser instanciada para que eu pudesse chamar o método Resolve na class. Como resultado, eu estava pensando nas linhas de tornar o contêiner de unidade globalmente disponível como estático ou envolvê-lo em uma class singleton.

    Eu li as respostas aqui e realmente não entendi o que estava sendo explicado. O que finalmente me ajudou a “entender” foi este artigo:

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

    E este parágrafo em particular foi o momento da “lâmpada”:

    “99% de sua base de código não deve ter conhecimento do contêiner IoC. É apenas a class raiz ou o bootstrapper que usa o contêiner e, mesmo assim, uma única chamada de resolução é normalmente necessária para criar seu gráfico de dependência e iniciar pedido ou pedido. “

    Este artigo me ajudou a entender que, na verdade, não devo acessar o contêiner de unidade em todo o aplicativo, mas apenas na raiz do aplicativo. Então devo aplicar o princípio DI repetidamente todo o caminho de volta para a class raiz do aplicativo.

    Espero que isso ajude os outros que estão tão confusos quanto eu! 🙂

    Você não precisa realmente usar seu contêiner diretamente em muitos lugares da sua aplicação. Você deve pegar todas as suas dependencies no construtor e não alcançá-las de seus methods. Seu exemplo poderia ser algo assim:

     public class TestSuiteParser : ITestSuiteParser { private TestSuite testSuite; public TestSuitParser(TestSuit testSuite) { this.testSuite = testSuite; } TestSuite Parse(XPathNavigator someXml) { List 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); } } } 

    E então você faz da mesma maneira em todo o aplicativo. Você, é claro, em algum momento terá que resolver alguma coisa. Em asp.net mvc por exemplo, este lugar está na fábrica do controlador. Essa é a fábrica que cria o controlador. Nesta fábrica, você usará seu contêiner para resolver os parâmetros do seu controlador. Mas este é apenas um lugar em toda a aplicação (provavelmente mais alguns lugares quando você faz coisas mais avançadas).

    Há também um bom projeto chamado CommonServiceLocator . Este é um projeto que possui uma interface compartilhada para todos os recipientes ioc populares, para que você não tenha dependência de um contêiner específico.

    Se apenas um poderia ter um “ServiceLocator” que é passado em torno de construtores de serviços, mas de alguma forma consegue “declarar” as dependencies pretendidas da class que está sendo injetada (ou seja, não ocultar as dependencies) … dessa forma, todos (? ) objeções ao padrão localizador de serviço podem ser colocadas em repouso.

     public class MyBusinessClass { public MyBusinessClass(IServiceResolver locator) { //keep the resolver for later use } } 

    Infelizmente, o acima, obviamente, só existirá em meus sonhos, já que c # proíbe parâmetros genéricos de variables ​​(ainda), então adicionar manualmente uma nova interface genérica toda vez que precisar de um parâmetro genérico adicional, seria complicado.

    Se por outro lado, o acima poderia ser alcançado apesar da limitação de c # da seguinte maneira …

     public class MyBusinessClass { public MyBusinessClass(IServiceResolver>> locator) { //keep the resolver for later use } } 

    Desta forma, só é necessário fazer digitação extra para conseguir a mesma coisa. O que eu ainda não tenho certeza é se, dado o design apropriado da class TArg (eu assumo que uma inheritance inteligente será empregada para permitir o aninhamento infinito de parâmetros TArg Generic), contêineres DI serão capazes de resolver o IServiceResolver corretamente. A ideia, em última análise, é simplesmente passar a mesma implementação do IServiceResolver não importa a declaração genérica encontrada no construtor da class que está sendo injetada.