Инъекция зависимостей против местоположения службы

В настоящее время я взвешиваю преимущества и недостатки между DI и SL. Тем не менее, я нашел себя в следующем catch 22, который подразумевает, что я должен просто использовать SL для всего и только вводить контейнер IoC в каждый класс.

DI Catch 22:

Некоторые зависимости, такие как Log4Net, просто не подходят для DI. Я называю эти мета-зависимости и чувствую, что они должны быть непрозрачными для вызова кода. Мое оправдание состоит в том, что если простой класс "D" изначально был реализован без регистрации, а затем стал требовать регистрации, тогда зависимые классы "A" , "B" и "C" теперь должны каким-то образом получить эту зависимость и передать ее из "A" - "D" (при условии, что "A" составляет "B" , "B" составляет "C" и т.д.). Теперь мы внесли значительные изменения в код только потому, что нам требуется вести журнал в одном классе.

Поэтому нам нужен непрозрачный механизм для получения мета-зависимостей. Двое приходят на ум: Синглтон и SL. Первый из них знает ограничения, прежде всего в отношении жестких возможностей обзора: в лучшем случае Singleton будет использовать абстрактный Factory, который хранится в области приложения (то есть в статической переменной). Это дает некоторую гибкость, но не идеальна.

Лучшим решением было бы добавить контейнер IoC в такие классы, а затем использовать SL из этого класса для разрешения этих мета-зависимостей из контейнера.

Следовательно, поймать 22: потому что класс теперь вводится контейнером IoC, тогда почему бы не использовать его для разрешения всех других зависимостей?

Я очень благодарен за ваши мысли:)

Ответ 1

Поскольку класс теперь вводится контейнером IoC, то почему бы ему не использовать его для разрешения всех других зависимостей?

Использование шаблона локатора сервисов полностью поражает одну из основных точек инъекции зависимостей. Точка инъекции зависимостей заключается в том, чтобы сделать зависимости явными. Когда вы скрываете эти зависимости, не делая их явные параметры в конструкторе, вы больше не выполняете полноценную инъекцию зависимостей.

Это все конструкторы для класса с именем Foo (настроено на тему песни Johnny Cash):

Неправильно:

public Foo() {
    this.bar = new Bar();
}

Неправильно:

public Foo() {
    this.bar = ServiceLocator.Resolve<Bar>();
}

Неправильно:

public Foo(ServiceLocator locator) {
    this.bar = locator.Resolve<Bar>();
}

Справа:

public Foo(Bar bar) {
    this.bar = bar;
}

Только последнее делает зависимость от Bar явной.

Что касается ведения журнала, то есть правильный способ сделать это без его проникновения в ваш доменный код (он не должен, но если он делает, то вы используете период инъекции зависимостей). Удивительно, что контейнеры IoC могут помочь с этой проблемой. Начните здесь.

Ответ 2

Локатор сервисов является анти-шаблоном по причинам, превосходно описанным в http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx. Что касается регистрации, вы можете либо рассматривать это как зависимость так же, как и любой другой, и вводить абстракцию через конструктор или вложение свойств.

Единственная разница с log4net заключается в том, что для этого требуется тип вызывающего абонента, который использует эту услугу. Использование Ninject (или другого контейнера) Как узнать тип, запрашивающий службу? описывает, как вы можете это решить (он использует Ninject, но применим к любой контейнер IoC).

В качестве альтернативы вы можете подумать о регистрации в качестве проблемы с перекрестным соединением, что не подходит для сочетания с кодом вашей бизнес-логики, и в этом случае вы можете использовать перехват, который предоставляется многими контейнерами IoC. http://msdn.microsoft.com/en-us/library/ff647107.aspx описывает использование перехвата с Unity.

Ответ 3

Мое мнение таково, что это зависит. Иногда один лучше, а иногда и другой. Но я бы сказал, что вообще я предпочитаю ДИ. Для этого есть несколько причин.

  • Когда зависимость каким-то образом внедряется в компонент, ее можно рассматривать как часть ее интерфейса. Таким образом, пользователю проще предоставить эти зависимости, потому что они видны. В случае инъекции SL или Static SL эти зависимости скрыты, а использование компонента немного сложнее.

  • Вложенные зависимости лучше для модульного тестирования, потому что вы можете просто имитировать их. В случае SL вам придется снова установить зависимостей Locator + mock. Так что это больше работы.

Ответ 4

Иногда протоколирование может быть реализовано с помощью AOP, поэтому оно не смешивается с бизнес-логикой.

В противном случае возможны следующие варианты:

  • используйте дополнительную зависимость (например, свойство setter), а для unit test вы не вводите ни одного регистратора. Контейнер IOC позаботится о том, чтобы установить его автоматически для вас, если вы работаете в процессе производства.
  • Когда у вас есть зависимость, которую использует почти каждый объект вашего приложения (объект "logger" является самым большим примером), это один из немногих случаев, когда однократная анти-модель становится хорошей практикой. Некоторые люди называют эти "хорошие синглтоны" Окружающим контекстом: http://aabs.wordpress.com/2007/12/31/the-ambient-context-design-pattern-in-net/

Конечно, этот контекст должен быть настраиваемым, чтобы вы могли использовать stub/mock для модульного тестирования. Другое предлагаемое использование AmbientContext заключается в том, чтобы разместить там текущий поставщик дата/время, чтобы вы могли его заглушить во время unit test и ускорить время, если хотите.

Ответ 5

Если в примере используется только log4net как зависимость, вам нужно только это сделать:

ILog log = LogManager.GetLogger(typeof(Foo));

Нет смысла вводить зависимость, поскольку log4net обеспечивает подробное ведение журнала, беря тип (или строку) в качестве параметра.

Кроме того, DI не коррелирует с SL. IMHO целью ServiceLocator является разрешение дополнительных зависимостей.

Например: если SL предоставляет интерфейс ILog, я напишу logging daa.

Ответ 6

Я использовал фреймворк Google Guice DI на Java и обнаружил, что он делает гораздо больше, чем упрощает тестирование. Например, мне нужен отдельный журнал для каждого приложения (не класса), с дополнительным требованием, чтобы весь мой общий код библиотеки использовал регистратор в текущем контексте вызова. Инжекция регистратора сделала это возможным. Разумеется, весь код библиотеки необходимо было изменить: регистратор был введен в конструкторы. Сначала я сопротивлялся этому подходу из-за всех необходимых изменений кодирования; в конце концов я понял, что изменения имеют много преимуществ:

  • Код стал проще
  • Код стал гораздо более надежным
  • Зависимости класса стали очевидными.
  • Если было много зависимостей, это было четкое указание на то, что классу необходимо было рефакторинг
  • Статические синглтоны были устранены.
  • Не удалось удалить объекты сеанса или контекста.
  • Многопоточность стала намного проще, поскольку контейнер DI мог быть построен, чтобы содержать только один поток, что устраняет непреднамеренное перекрестное загрязнение.

Излишне говорить, что теперь я большой поклонник DI и использую его для всех, кроме самых тривиальных приложений.

Ответ 7

Мы приземлились на компромисс: используйте DI, но свяжите зависимости верхнего уровня с объектом, избегая рефакторинга ада, если эти зависимости меняются.

В приведенном ниже примере мы можем добавить в "ServiceDependencies" без необходимости реорганизации всех зависимых от производных.

Пример:

public ServiceDependencies{
     public ILogger Logger{get; private set;}
     public ServiceDependencies(ILogger logger){
          this.Logger = logger;
     }
}

public abstract class BaseService{
     public ILogger Logger{get; private set;}

     public BaseService(ServiceDependencies dependencies){
          this.Logger = dependencies.Logger; //don't expose 'dependencies'
     }
}


public class DerivedService(ServiceDependencies dependencies,
                              ISomeOtherDependencyOnlyUsedByThisService                       additionalDependency) 
 : base(dependencies){
//set local dependencies here.
}

Ответ 8

Я знаю, что люди действительно говорят, что DI - единственный хороший образец IOC, но я этого не понимаю. Я постараюсь немного продать SL. Я буду использовать новую структуру MVC Core, чтобы показать вам, что я имею в виду. Первые двигатели DI действительно сложны. То, что люди на самом деле подразумевают, когда говорят DI, использует некоторые рамки, такие как Unity, Ninject, Autofac..., которые делают для вас все тяжелый подъем, где SL может быть таким же простым, как создание класса factory. Для небольшого быстрого проекта это простой способ сделать IOC, не изучая целую структуру для правильного DI, они могут быть не такими трудными для изучения, но все же. Теперь к проблеме, которую может стать DI. Я буду использовать цитату из документов MVC Core. "ASP.NET Core разработан с нуля, чтобы поддерживать и использовать инъекции зависимостей". Большинство людей говорят, что о DI "99% вашей базы кода не должны знать ваш контейнер IoC". Так почему же им нужно будет проектировать с нуля, если только 1% кода должно знать об этом, не поддерживает ли старый MVC DI? Ну, это большая проблема DI, это зависит от DI. Все, что работает, "КАК ЭТО СЛЕДУЕТ СДЕЛАТЬ", требует много работы. Если вы посмотрите на новый Action Injection, это не зависит от DI, если вы используете атрибут [FromServices]. Теперь люди DI скажут "НЕТ", вы полагаете, что пойти с Фабриками не так, но, как вы видите, даже люди, делающие MVC, не ошибались. Проблема DI видна в фильтрах, а также посмотрите, что вам нужно сделать, чтобы получить DI в фильтре

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation("Business action starting...");
            // perform some business logic work

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // perform some business logic work
            _logger.LogInformation("Business action completed.");
        }
    }
}

Если вы использовали SL, вы могли бы сделать это с помощью var _logger = Locator.Get();. И затем мы приходим к представлениям. При всем хорошем отношении относительно DI им пришлось использовать SL для просмотров. новый синтаксис @inject StatisticsService StatsService совпадает с var StatsService = Locator.Get<StatisticsService>();. Наиболее рекламируемой частью DI является модульное тестирование. Но то, что люди и что делает, просто тестирует там ложные услуги без каких-либо целей или вынуждает подключить туда двигатель DI для проведения реальных тестов. И я знаю, что вы можете сделать что-то плохое, но люди в конечном итоге создают локатор SL, даже если они не знают, что это такое. Там, где многие люди не делают DI, даже не читая его сначала. Моя самая большая проблема с DI заключается в том, что пользователь класса должен знать внутреннюю работу класса в другом, чтобы использовать его.
SL может быть использован в хорошем смысле и имеет некоторые преимущества, прежде всего его простоту.

Ответ 9

Это касается "Локатора сервисов - это анти-шаблон" Марка Симена. Возможно, я ошибаюсь. Но я просто подумал, что должен поделиться своими мыслями.

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

Метод Process() для OrderProcessor фактически не соответствует принципу "Инверсия управления". Он также нарушает принцип единой ответственности на уровне метода. Почему метод должен быть связан с созданием экземпляра

(через новый или любой класс S.L.) ему нужно что-то выполнить.

Вместо того, чтобы метод Process() создавал объекты, конструктор может фактически иметь параметры для соответствующих объектов (читать зависимости), как показано ниже. Тогда КАК может быть, что локатор сервисов отличается от IOC

контейнера. И это также поможет в модульном тестировании.

public class OrderProcessor : IOrderProcessor
{
    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
    {
        this.validator = validator; 
        this.shipper = shipper;
    }

    public void Process(Order order)
    {

        if (this.validator.Validate(order))
        {
            shipper.Ship(order);
        }
    }
}


//Caller
public static void main() //this can be a unit test code too.
{
var validator = Locator.Resolve<IOrderValidator>(); // similar to a IOC container 
var shipper = Locator.Resolve<IOrderShipper>();

var orderProcessor = new OrderProcessor(validator, shipper);
orderProcessor.Process(order);

}

Ответ 10

Я знаю, что этот вопрос немного стар, я просто подумал, что дам свой вклад.

В действительности, 9 раз из 10 вы действительно не нуждаетесь SL и должны полагаться на DI. Однако есть случаи, когда вы должны использовать SL. Одна область, в которой я нахожу себя использующей SL (или ее вариант), находится в разработке игр.

Другим преимуществом SL (на мой взгляд) является способность проходить вокруг классов internal.

Ниже приведен пример:

internal sealed class SomeClass : ISomeClass
{
    internal SomeClass()
    {
        // Add the service to the locator
        ServiceLocator.Instance.AddService<ISomeClass>(this);
    }

    // Maybe remove of service within finalizer or dispose method if needed.

    internal void SomeMethod()
    {
        Console.WriteLine("The user of my library doesn't know I'm doing this, let keep it a secret");
    }
}

public sealed class SomeOtherClass
{
    private ISomeClass someClass;

    public SomeOtherClass()
    {
        // Get the service and call a method
        someClass = ServiceLocator.Instance.GetService<ISomeClass>();
        someClass.SomeMethod();
    }
}

Как вы можете видеть, пользователь библиотеки не имеет представления, что этот метод был вызван, потому что мы не сделали DI, а не то, что мы все равно можем.