Внедрение зависимостей и расположение службы

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

DI Catch 22:

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

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

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

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

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


person Lawrence Wagerfield    schedule 13.02.2011    source источник
comment
Ваш комментарий о необходимости передать его от A- ›B-› C- ›D предполагает, что вы путаете время выполнения и время создания. См. misko.hevery.com/ 30 марта 2009 г. / Collaborator-vs-the-factory /   -  person WW.    schedule 18.02.2011
comment
Отличный комментарий WW, который честно помог исправить большой умственный блок, который у меня возник в связи с DI.   -  person Lawrence Wagerfield    schedule 21.02.2011
comment
ИМХО расположение службы - это инверсия управления бедняками, она дает вам некоторые преимущества надлежащего IoC, но, не будучи явным, также снижает многие из его преимуществ. Внедрение зависимостей - вот где оно.   -  person kay.one    schedule 10.08.2011


Ответы (11)


Поскольку класс теперь внедряется с контейнером 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 могут помочь в решении этой проблемы. Начните здесь.

person jason    schedule 13.02.2011
comment
Я полностью согласен с тем, что последнее является наиболее ясным и явным. Однако разве вы не согласны с тем, что такие классы, как Log4Net, не следует вводить? - person Lawrence Wagerfield; 13.02.2011
comment
@ Лоуренс Вагерфилд: Я не согласен. Пожалуйста, прочтите мою правку о правильном способе ведения журнала. Либо введите их, либо используйте подход АОП. - person jason; 13.02.2011
comment
@Jason: Я бы не сказал, что главная задача DI - сделать зависимости явными, а не сделать их инъекционными. Большинство людей считают внедрение ctor / property неоспоримым преимуществом, но не все. Я думаю, что оригинальный плакат поднимает важный вопрос с прагматической точки зрения. - person Thiru; 04.07.2012
comment
Думаю, что в 4-м примере должно быть this.bar = bar; вместо this.Bar = bar; - person BSeitkazin; 26.03.2015

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

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

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

person devdigital    schedule 13.02.2011
comment
Расположение услуги - не антипаттерн. Это просто шаблон, который часто используют неправильно. Цель состоит в разрешении времени выполнения на основе переменных данных. Если у вас есть доступ к информации во время конструктора, вы, вероятно, не захотите напрямую использовать локатор. - person Chris Marisic; 10.04.2015
comment
Для разрешения времени выполнения на основе переменных данных у вас есть фабрики - созданные вручную (предпочтительно) или на основе контейнеров. - person devdigital; 10.04.2015
comment
фабрики по определению могут возвращать только новые экземпляры, что исключает возможность использования объектов с ограниченным сроком действия, если вы разрешаете ему возвращать что-либо с ограниченным сроком действия ... это локатор. - person Chris Marisic; 10.04.2015
comment
Нет, если фабрика содержит ссылку на контейнер (локатор) или является фабрикой контейнеров. В любом случае желательно ограничить использование локаторов, и они все равно должны рассматриваться как антипаттерн. - person devdigital; 11.04.2015
comment
Я согласен, что вы хотите ограничить их использование, но это абсолютно не антипаттерн. Взрыв локаторов можно рассматривать как антипаттерн, так же как взрыв чего угодно - антипаттерн. - person Chris Marisic; 13.04.2015
comment
Все ссылаются на эту статью о том, что локатор сервисов является анти-шаблоном, но они не знают, когда шаблон применяется надлежащим образом. Приведенный пример - неправильное использование шаблона локатора сервисов. Локаторы сервисов предназначены для подключаемого поведения, когда многие конкретные реализации реализуют один и тот же интерфейс, и вы выбираете реализацию на основе некоторых условий, конфигурации или решения, основанного на данных. Это правда, что их следует использовать редко, но не потому, что это антипаттерн, а потому, что ситуация, когда это уместно, встречается редко. Как и все шаблоны, его не следует использовать вслепую. - person AaronLS; 11.06.2021

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

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

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

person Oleg Rudckivsky    schedule 13.02.2011

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

В противном случае варианты:

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

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

person frecil    schedule 12.04.2012

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

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

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

person sabra    schedule 08.05.2013

Мы пришли к компромиссу: используйте 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.
}
person BlackjacketMack    schedule 18.03.2016

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

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

}
person Aniket Bhoyar    schedule 01.06.2017

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

На самом деле в 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, и в любом случае мы не сможем это сделать.

person Mathew O'Dwyer    schedule 14.04.2018

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

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

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

Кроме того, DI не коррелирует с SL. IMHO цель ServiceLocator - разрешить необязательные зависимости.

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

person Rafael Diego Nicoletti    schedule 06.07.2012

Я знаю, что люди действительно говорят, что DI - это единственный хороший шаблон IOC, но я этого не понимаю. Постараюсь немного продать 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. Чтобы заставить все работать «КАК ЭТО ДОЛЖНО БЫТЬ СДЕЛАНО», требуется много работы. Если вы посмотрите на новую инъекцию действия, это не зависит от DI, если вы используете атрибут [FromServices]. Теперь DI люди скажут НЕТ, вы должны использовать Factories, а не это, но, как вы можете видеть, даже люди, делающие 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 можно использовать хорошо, и он имеет некоторые преимущества, прежде всего его простота.

person Filip Cordas    schedule 01.10.2016

Для DI вам нужна жесткая ссылка на сборку внедренного типа? Я не вижу, чтобы кто-нибудь об этом говорил. Для SL я могу указать своему преобразователю, где динамически загружать мой тип, когда это необходимо, из config.json или аналогичного. Кроме того, если ваша сборка содержит несколько тысяч типов и их наследование, нужны ли вам тысячи каскадных вызовов поставщику службы для их регистрации? Вот о чем я действительно вижу много разговоров. Большинство из них говорят о преимуществах DI и о том, что это такое в целом, когда дело доходит до того, как реализовать его в .net, они представили метод расширения для добавления ссылки на сборку жестко связанных типов. Для меня это не очень разобщает.

person FTLTraveller    schedule 04.08.2019