it-swarm.dev

Czy lepiej jest utworzyć singleton, aby uzyskać dostęp do kontenera jedności lub przekazać go przez aplikację?

Zanurzam palec w ramie IoC i wybrałem Unity. Jedną z rzeczy, których wciąż nie do końca rozumiem, jest sposób rozwiązywania obiektów głębiej w aplikacji. Podejrzewam, że po prostu nie miałem żarówki na moment, która to wyjaśni.

Próbuję więc zrobić coś podobnego do następującego w kodzie psuedo'ish

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

Tak więc testSuiteParser.Parse wykonuje następujące czynności

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);
    } 
}

Widzę trzy opcje:

  1. Utwórz Singleton, aby przechowywać mój kontener jedności, do którego mogę uzyskać dostęp w dowolnym miejscu. Naprawdę nie jestem fanem tego podejścia. Dodanie zależności w taki sposób, aby użyć struktury wstrzykiwania zależności, wydaje się nieco dziwne.
  2. Przekaż IUnityContainer do mojej klasy TestSuiteParser i każdego jej dziecka (zakładając, że jest n poziomów głęboko lub w rzeczywistości około 3 poziomów głębokości). Przekazywanie IUnityContainer wszędzie jest dziwne. Może po prostu muszę się z tym pogodzić.
  3. Miej moment żarówki w odpowiedni sposób, aby korzystać z Unity. Mając nadzieję, że ktoś może pomóc, przestawić przełącznik.

[EDYCJA] Jedną z rzeczy, na których nie miałem jasności, jest to, że chcę utworzyć nową instancję przypadku testowego dla każdej iteracji instrukcji foreach. Powyższy przykład musi przeanalizować konfigurację zestawu testowego i zapełnić zbiór obiektów przypadków testowych

48
btlog

Prawidłowe podejście do DI polega na użyciu Wtrysk konstruktora lub innego wzorca DI (ale Wtrysk Konstruktora jest najczęstszy), aby wprowadzić zależności do konsumenta, niezależnie od DI Container .

W twoim przykładzie wygląda na to, że potrzebujesz zależności TestSuite i TestCase, więc twoja klasa TestSuiteParser powinna statically ogłosić że wymaga tych zależności, pytając o nie za pośrednictwem swojego (tylko) konstruktora:

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

    // ...
}

Zwróć uwagę, że kombinacja słowa kluczowego readonly i klauzuli Guard chroni niezmienniki klasy, zapewniając, że zależności will będą dostępne dla każdej pomyślnie utworzonej instancji TestSuiteParser.

Możesz teraz zaimplementować metodę Parse w następujący sposób:

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

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

(jednak podejrzewam, że może istnieć więcej niż jedna TestCase, w takim przypadku możesz chcieć wstrzyknąć Fabrykę Abstrakcyjną zamiast pojedynczej TestCase.)

Z katalogu głównego Composition Root możesz skonfigurować Unity (lub dowolny inny kontener):

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

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

Gdy kontener rozwiąże TestSuiteParser, rozumie wzorzec wtrysku konstruktora, więc Auto-Wires instancja ze wszystkimi wymaganymi zależnościami.

Utworzenie kontenera Singleton lub przekazanie kontenera to tylko dwie odmiany deseniu Service Locator , więc nie poleciłbym tego.

51
Mark Seemann

Jestem nowy w Dependency Injection i miałem również to pytanie. Usiłowałem skupić się na DI, głównie dlatego, że skupiałem się na zastosowaniu DI tylko do jednej klasy, nad którą pracowałem, a kiedy dodałem zależności do konstruktora, natychmiast spróbowałem znaleźć sposób na uzyskanie jedności kontener do miejsc, w których ta klasa musi zostać utworzona, aby móc wywołać metodę Resolve w klasie. W rezultacie zastanawiałem się nad sposobem, w jaki kontener jedności był globalnie dostępny jako statyczny lub zawijany w pojedynczą klasę.

Przeczytałem tutaj odpowiedzi i naprawdę nie rozumiałem, o co mi chodzi. Tym, co w końcu pomogło mi to „zdobyć” był ten artykuł:

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

Ten akapit w szczególności był momentem „żarówki”:

"99% twojej bazy kodu nie powinno mieć wiedzy o twoim kontenerze IoC. Tylko klasa root lub bootstrapper, która używa kontenera i nawet wtedy, pojedyncze wywołanie rozstrzygające to wszystko, co jest zazwyczaj konieczne do zbudowania wykresu zależności i uruchom aplikację lub prośbę. ”

Ten artykuł pomógł mi zrozumieć, że faktycznie nie mogę uzyskać dostępu do kontenera jedności w całej aplikacji, ale tylko w katalogu głównym aplikacji. Muszę więc wielokrotnie stosować zasadę DI aż do klasy głównej aplikacji.

Mam nadzieję, że pomoże to innym, którzy są tak zagubieni jak ja! :)

12
BruceHill

Nie powinieneś tak naprawdę używać kontenera bezpośrednio w wielu miejscach aplikacji. Powinieneś wziąć wszystkie swoje zależności w konstruktorze i nie dotrzeć do nich ze swoich metod. Przykładem może być coś takiego:

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);
        } 
    }
}

A potem robisz to w ten sam sposób w całej aplikacji. Oczywiście w pewnym momencie będziesz musiał coś rozwiązać. Na przykład w asp.net mvc to miejsce znajduje się w fabryce kontrolera. To jest fabryka, która tworzy kontroler. W tej fabryce użyjesz kontenera do rozwiązania parametrów kontrolera. Ale to tylko jedno miejsce w całej aplikacji (prawdopodobnie więcej miejsc, gdy robisz bardziej zaawansowane rzeczy).

Istnieje również projekt Nicea o nazwie CommonServiceLocator . Jest to projekt, który ma wspólny interfejs dla wszystkich popularnych kontenerów ioc, dzięki czemu nie masz zależności od konkretnego kontenera.

4

Gdyby tylko jeden mógł mieć „ServiceLocator”, który byłby przekazywany do konstruktorów usług, ale w jakiś sposób udaje mu się „zadeklarować” zamierzone zależności klasy, do której jest wprowadzany (tj. Nie ukrywać zależności) ... w ten sposób wszystko (? ) zastrzeżenia do wzoru lokalizatora usługi mogą zostać wstrzymane.

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

Niestety, powyższe oczywiście będzie istniało tylko w moich snach, ponieważ c # zabrania zmiennych ogólnych parametrów (nadal), więc ręczne dodawanie nowego ogólnego interfejsu za każdym razem, gdy potrzebny jest dodatkowy parametr ogólny, byłoby niewygodne.

Jeśli z drugiej strony można to osiągnąć pomimo ograniczenia c # w następujący sposób ...

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

W ten sposób wystarczy wykonać dodatkowe wpisywanie, aby osiągnąć to samo. Nie jestem jeszcze pewien, czy, biorąc pod uwagę prawidłowy projekt klasy TArg (zakładam, że sprytne dziedziczenie zostanie zastosowane, aby umożliwić nieskończone zagnieżdżanie parametrów TArg Ogólne), kontenery DI będą w stanie poprawnie rozwiązać IServiceResolver. Ostatecznym pomysłem jest po prostu przekazanie tej samej implementacji zmiennej IServiceResolver bez względu na ogólną deklarację znajdującą się w konstruktorze klasy, do której jest wprowadzana.

0
Dantte