Подтвердить что ты не робот

Лучше ли создать одноэлементный доступ к контейнеру единства или передать его через приложение?

Я окунаюсь в использование рамки 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);
    } 
}

Я вижу три варианта:

  • Создайте Singleton, чтобы сохранить контейнер единства, с которым я могу получить доступ в любом месте. Я действительно не поклонник такого подхода. Добавление такой зависимости, чтобы использовать инфраструктуру инъекций зависимостей, кажется немного на нечетной стороне.
  • Передайте IUnityContainer в мой класс TestSuiteParser и каждый его дочерний элемент (предположим, что это n уровней глубоко или на самом деле около 3 уровней). Прохождение IUnityContainer повсюду просто выглядит странно. Мне просто нужно преодолеть это.
  • Имейте момент лампочки на правильном пути использования Unity. Надеясь, кто-то может помочь щелкнуть переключателем.

[EDIT] Одна из вещей, о которых я не понимал, - это то, что я хочу создать новый экземпляр тестового примера для каждой итерации инструкции foreach. В приведенном выше примере необходимо проанализировать конфигурацию тестового набора и заполнить коллекцию объектов тестового случая

4b9b3361

Ответ 1

Правильный подход к 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); 
    }  
} 

(однако, я подозреваю, что может быть задействовано более одной TestCase, и в этом случае вы можете добавить абстрактный Factory вместо одной TestCase.)

Из Корень композиции вы можете настроить Unity (или любой другой контейнер):

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

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

Когда контейнер разрешает TestSuiteParser, он понимает шаблон инсталляции конструктора, поэтому он Auto-Wires экземпляр со всеми необходимыми зависимостями.

Создание контейнера Singleton или передача контейнера - это всего лишь две вариации антивирусная программа Locator,, поэтому я бы не рекомендовал что.

Ответ 2

Я новичок в Dependency Injection, и у меня также был этот вопрос. Я изо всех сил пытался разобраться с DI, главным образом потому, что я сосредоточился на применении DI только к одному классу, над которым я работал, и как только я добавил зависимости к конструктору, я сразу же попытался найти способ добиться единства контейнер в те места, где этот класс необходимо создать, чтобы я мог вызвать метод Resolve в классе. В результате я думал о том, как сделать контейнер единства глобально доступным как статический или обернуть его в одноэлементном классе.

Я прочитал ответы здесь и не совсем понял, что объясняется. Что, наконец, помогло мне "получить это" в этой статье:

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

И этот пункт, в частности, был моментом "лампочка":

"99% вашей базы кода не должны знать ваш контейнер IoC. Только корневой класс или загрузочный носитель использует контейнер, и даже тогда один единственный вызов разрешения - это все, что обычно необходимо для построения графика зависимости и запустите приложение или запрос.

Эта статья помогла мне понять, что на самом деле я не должен иметь доступ к контейнеру единства по всему приложению, но только в корне приложения. Поэтому я должен повторно применять принцип DI на обратном пути к корневому классу приложения.

Надеюсь, это поможет другим, которые так же запутались, как и я!:)

Ответ 3

Вам действительно не нужно использовать свой контейнер непосредственно во многих местах вашего приложения. Вы должны брать все свои зависимости в конструкторе и не добираться до них из своих методов. Вы могли бы выглядеть примерно так:

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 это место находится в контроллере factory. Это factory, который создает контроллер. В этом factory вы будете использовать свой контейнер для разрешения параметров вашего контроллера. Но это всего лишь одно место во всем приложении (возможно, еще несколько мест, когда вы делаете более продвинутые вещи).

Существует также хороший проект под названием CommonServiceLocator. Это проект, который имеет общий интерфейс для всех популярных контейнеров ioc, поэтому у вас нет зависимости от конкретного контейнера.

Ответ 4

Если только один может иметь "ServiceLocator", который передается вокруг конструкторов служб, но каким-то образом "Объявляет" предполагаемые зависимости класса, в который он вводится (т.е. не скрывает зависимости)... таким образом, все (?) возражения к шаблону локатора сервисов можно отложить.

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

К сожалению, вышеупомянутое, очевидно, будет когда-либо существовать во сне, поскольку С# запрещает переменные общие параметры (все еще), поэтому вручную добавлять новый общий интерфейс каждый раз, когда вам нужен дополнительный общий параметр, будет громоздким.

Если, с другой стороны, вышеупомянутое может быть достигнуто, несмотря на ограничение С# следующим образом...

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

Таким образом, для достижения одной и той же вещи нужно только сделать дополнительную типизацию. Я еще не уверен, что если с учетом правильного дизайна класса TArg (я предполагаю, что умное наследование будет использоваться для обеспечения бесконечного вложения TArg общих параметров), контейнеры DI смогут разрешить IServiceResolver правильно. Идея, в конечном счете, состоит в том, чтобы просто обойти ту же самую реализацию IServiceResolver независимо от того, какое общее объявление найдено в конструкторе класса, в которое он вводится.