Внедрение зависимостей и расположение службы - PullRequest
35 голосов
/ 13 февраля 2011

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

DI Catch 22:

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

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

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

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

Буду очень признателен за ваши мысли:)

Ответы [ 10 ]

58 голосов
/ 13 февраля 2011

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

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

Это все конструкторы для класса с именем Foo (установлен в темупесня Джонни Кэша):

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

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 могут помочь с этой проблемой.Начало здесь .

8 голосов
/ 13 февраля 2011

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

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

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

5 голосов
/ 13 февраля 2011

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

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

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

4 голосов
/ 12 апреля 2012

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

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

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

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

1 голос
/ 14 апреля 2018

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

На самом деле, 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's 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, а не то, что мы все равно могли.

1 голос
/ 01 июня 2017

Это касается «Сервисного локатора - анти-паттерна» Марка Симана. Я могу ошибаться здесь. Но я просто подумал, что тоже должен поделиться своими мыслями.

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

}
1 голос
/ 18 марта 2016

Мы нашли компромисс: используйте 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.
}
1 голос
/ 08 мая 2013

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

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

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

0 голосов
/ 01 октября 2016

Я знаю, что люди действительно говорят, что DI - это единственная хорошая модель МОК, но я не понимаю этого.Я постараюсь немного продать SL.Я буду использовать новую платформу MVC Core, чтобы показать вам, что я имею в виду.Первые двигатели DI действительно сложны.Что люди действительно имеют в виду, когда говорят «DI», так это используют некоторые фреймворки, такие как Unity, Ninject, Autofac ... которые делают всю тяжелую работу за вас, где SL может быть таким же простым, как создание фабричного класса.Для небольших быстрых проектов это простой способ сделать 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 можно использовать хорошим способом и имеет некоторые преимущества, прежде всего его простоту.

0 голосов
/ 06 июля 2012

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

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

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

Кроме того, DI не коррелирует с SL.ИМХО, целью ServiceLocator является разрешение необязательных зависимостей.

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

...