Pull to refresh

Comments 74

Service Locator нарушает инкапсуляцию класса, если он используется непосредственно в классе. Но даже в данном примере, что нам мешает использовать Service Locator для определения параметров вызова конструктора?

И не понял к чему уточнение про статически типизированные? На том же PHP я избегаю зависимости моих классов от Service Locator (даже явной, через параметры), предпочитая получать в конструкторе или сеттерах конкретные инстансы классов/интерфейсов.

В любом случае, даже неявная (с точки зрения публичного API) зависимость класса от Service Locator по-моему лучше чем захардкоженная зависимость от конкретных классов или, пускай, интерфейсов как в примере.
В целом, внедрения через аргументы вполне достаточно. И коль уж речь зашла о PHP…
//Yii2
Yii::createObject(Some::class);

//Laravel5
app(Some::class)

Этим я хотел сказать, что вы всегда вызываете авторезолвер явным образом, и вы должны прекрасно понимать, что это не тоже самое, что:
$some = new Some;

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

Если вы в Laravel пишете app(Some::class) только в Service Providers (впрочем, там, скорее, будет $this->app) — это DI, если пишете где попало — это Service Locator. Ларавеловские «фасады» — это, кстати, тот же service locator с теми же проблемами.
Не знаю как в PHP, могу сказать за WPF и ASP.NET. В такого рода приложениях мы находим централизованное место и через него и только через него происходит resolve и больше нигде!
В PHP точно так же, от языка тут вряд ли что-то зависит.
Я тоже так думаю. Это называется принципом Голливуда: «не звоните нам, мы сами вам позвоним». Хотя я не очень люблю эту метафору применительно к DI с помощью IoC.
Service Locator нарушает инкапсуляцию класса, если он используется непосредственно в классе. Но даже в данном примере, что нам мешает использовать Service Locator для определения параметров вызова конструктора?

Мы решили проблему, перенеся ответственность для клиента и сделали зависимости очевидными. Если клиент будет использовать Service Locator — право клиента, значит сам себе злобный буратино.

На том же PHP я избегаю зависимости моих классов от Service Locator (даже явной, через параметры), предпочитая получать в конструкторе или сеттерах конкретные инстансы классов/интерфейсов.

Извините, не совсем понял. Чем отличается ваш подход от инжектирования зависимостей через конструктор?

по-моему лучше чем захардкоженная зависимость от конкретных классов или, пускай, интерфейсов как в примере.

Если это reusable business-object, то чем будет лучше сокрытие зависимостей от клиента?
Но даже в данном примере, что нам мешает использовать Service Locator для определения параметров вызова конструктора?

Тем, что это означает, что вы вызываете конструктор напрямую. В приложении, построенном на инверсии зависимостей, это очень странно.
Конструктор вызывает сам же Service Locator :)
Тогда что-то вызвало сервис-локатор для получения этого объекта. Теперь все проблемы, описанные в статье, у вызывающего.
Всегда что-то будет вызывать или сервис-локатор, или new. Но мы можем волевым решением сгруппировать эти вызовы в нескольких строго определенных местах типа сервис-локатора и/или фабрик.
Всегда что-то будет вызывать или сервис-локатор, или new.

Не совсем так.

Скажем, если у меня есть MVC (или WebAPI) приложение, то с прикладной точки зрения точкой входа является метод в классе контроллера. Соответственно, я хочу, чтобы контроллер получал свои зависимости через инъекцию в конструктор, потому что это явный контракт, и это удобно для тестирования.

Дальше инфраструктура предоставляет мне фабрику контроллеров, которую я могу заменить так, как мне удобно. Внутри этой фабрики я могу, скажем, обратиться к DI-контейнеру, и сказать «дай мне экземпляр контроллера». Это не будет вызовом сервис-локатора.

(но вообще, конечно, вы правы в том, что при построении приложения, основанного на инверсии зависимостей, вся логика по созданию объектов концентрируется в одном месте)

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

В данном примере другой антипаттерн показан — чтобы использовать В надо дернуть/засетить/заинжектить А. К сервис-локатору это никак не относится.

Сервис локатор отвечает за то, чтобы вернуть обработчик заказов и здесь уже ответственность либо самого локатора, чтобы такой обработчик был у него создан, либо ответственность того, кто помещает в сервис локатор этого обработчика — design by contract.

Сервис-локатор похож скорее на один большой глобальный god-mode объект. Вот здесь его явный минус. Тот же code-completion можно «залочить» с помощью интерфейса который в одном себе отнаследует интерфейс каждого метода и тогда будет хотя бы видно, какие сервисы данный локатор предоставляет. Единственный минус — при очередном изменении надо будет это повторять, да и не всем о всех надо знать.
> К сервис-локатору это никак не относится.
Если обработчик заказов использует другой сервис, например склад сервис, то согласно паттерну SL он должен будет сделать Locator.Resolve<IWarehouseService>().

Аргументы в статье валидные, но и были уже давно описаны Фаулером в популярной статье, Inversion of Control Containers and the Dependency Injection pattern.
В данном примере другой антипаттерн показан — чтобы использовать В надо дернуть/засетить/заинжектить А.

В вашем коде объекты не зависят от других объектов?
В любом коде они зависят. Но если мне объект предоставляет интерфейс АйСделатьХорошо, то этот объект ДОЛЖЕН по своему интерфейсу уже делать мне хорошо. Я — клиент и я не должен зависеть от деталей реализации того, кто за интерфейсом. Иначе смысл теряется в-принципе в интерфейсах, DI и SL.
Если объекту А для предоставления своих услуг нужен другой объект B, и только объект A знает как воспользоваться B так, чтобы предоставить свои услуги корректно, то что в этом не так?
Клиенту объекта А это должно быть фиолетово ибо инкапусуляция в том и есть, что интерфейс уже гарантирует, что с той стороны всё будет работать.

Проблемы имплементации — это её личные проблемы и они не должны выходить за интерфейс. Если получается так, что клиенту объекта А надо задумываться над деталями имплементации, то уже что-то не так в архитектуре и точно не сервис-локатор виноват, а автор такого объекта/интерфейса, который не может гарантировать себя.
Если вы программируете на интерфейсах, и в вашем приложении используется инверсия зависимостей, вы, скорее всего, практически нигде не будете создавать объект, реализующий какой-то интерфейс. Таким образом, проблема «надо что-то заинжектить» вас коснуться и не должна — вы получаете готовый работоспособный объект.
Почему-то слова Dependency Injection в статье расположены слишком далеко от примера с конструктором
Я может чего-то не понимаю, но разве любые Service Locator, DI, аспект-ориентированное программирование, почти любая ORM в теории и так нарушают инкапсуляцию просто по факту своего существования, там как практически все они построены на рефлексии и изменении значений в том числе и приватных полей классов (что в теории считается явным нарушением инкапсуляции)?
Поэтому теперь надо повсюду, даже если вы не пишите монструозный EF, использовать рефлекшн для изменения приватных полей и прикрываться тем, что существуют популярные фрэймворки, которые так делают?
Я этого не говорил, рефлекшен кроме orm и т.п. библиотек зло почти всегда. Я говорил о том, что говорить что Service Locator нарушает инкапсуляции просто потому что у него такой принцип работы, как и у DI и AOP в общем случае.
Вы чего-то не понимаете. И Service Locator, и Dependency Injection можно реализовать вообще не используя рефлексию (просто будет много однотипного кода). Если же рефлексию разрешить, то выясняется, что для решения типовых задач инверсии зависимостей никакого нарушения инкапсуляции (в значении «обхода областей видимости») не нужно: параметры конструктора — публичны, свойства, через которые делается вбрасываение — тоже публичны, ну и так далее.
Можно вопрос не в этом. Не знаю как в Net, но в Java почти любой DI, аспекто-ориентированный фреймворк и ORM может работать с приватными полями, причем практически любого класса, даже если автор класс на это вообще не рассчитывал. Например, можно взять и аспектами изменить работу всех методах всех классов, добавив логирования к любому не финальному методу. С одной стороны, да это очень удобно, с другой стороны, о теоретической «чистой» инкапсуляции тут уже сложно говорить.
А давайте отделять мух от котлет?

Во-первых, давайте сразу выкинем из обсуждения AOP и ORM — они не имеют отношения к статье. Остаются DI и SL.

Для того, чтобы реализовать DI, все, что нужно — это умение создавать объекты. Более того, это (вместе с трекингом жизненного цикла) более-менее и описывает всю необходимую функциональность DI-фреймворка. Если кто-то решил встроить туда AOP и для этого лезет в инкапсулированную область — это личное дело того, кто решил так сделать, к DI (или SL) как шаблону это отношения не имеет.

Поэтому нет, утверждение «любые Service Locator, DI, [...] и так нарушают инкапсуляцию просто по факту своего существования» — неверно.
Спасибо, поскольку вы всё доступно объяснили, от себя комментировать ничего не буду.
Я очень не люблю инъекцию в приватные поля в стиле Autowired.

При constructor injection сразу видно все зависимости класса. И сразу очевидны случаи, когда их слишком много. С приватными же полями очень легко внести 20 зависимостей и этого не заметить.
> С приватными же полями очень легко внести 20 зависимостей и этого не заметить.
Это и минус, и плюс инъекции. С одной стороны да — не хочется иметь много зависимостей. С другой — не хочется заботиться о них вообще.
Но по мне, так лучше 200 мелких зависимостей, чем один монолит. А потому, что 200 зависимостей сделают ровно ту задачу, которая от них требуется, чем монолит, который на 70% использоваться никогда не будет.
В итоге, вопрос упирается в грамотную архитектуру.
200 зависимостей — это по факту тот же монолит, только раздробленный в рандомную окрошку. Реюзабельность и тестируемость на нуле. В чистом виде самообман.

Заботиться надо не о зависимостях, а об архитектуре. Аббревиатура SOLID придумана не просто так. А если следовать этим принципам, много зависимостей — это четкий признак нарушения как минимум SRP.
В рандомную или нет — опять же, зависит от продуманности архитектуры. Можно и нашпиговать зависимостями так, что их никак не перекомпоновать, но разве это беда инъекции?

Много зависимостей — как и много параметров функции, требует перепланирования архитектуры. Но, в целом, в большом проекте может быть ооочень много транзитивных зависимостей.
И особенно четко об этом сигнализирует круговая транзитивная зависимость (circular dependencies).
Так никакой разницы между инъекцией в конструктор и инъекцией в приватные поля тут не будет. За тем исключением, что с инъекциями в конструктор намного очевиднее, насколько все запущено. Особенно полезно на code review: добавление приватного поля с autowired-аннотацией в диффе особо ни о чем не сигнализирует, а аргументы конструктора — вот они, все 25 :)
Все же есть — есть явное и неявное. Кроме того, аргументы конструктора требуются немедленного разрешения, при создании экземпляра, а инъекция в поля может быть отложенной, если не использовать особых хитрых техник проксирования.
Поскольку все плюсы и минусы есть, и они более или менее очевидны, не вижу состава спора — пусть кто-то выбирает себе по вкусу. Опять же, все зависит от трактовки «зависимость».
Насчет отложенной — многие реализации DI умеют делать такие прокси.

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

Второе — антипаттерн, так как сконструированный объект в данном случае не является полноценным.
А первое — просто неверно. И не надо никаких «хитрых техник», достаточно правильно указать тип аргумента:
  • Task<T> — зависимость может долго инициализироваться и может быть еще не готова к с использованию на момент конструирования
  • Lazy<T> — зависимого ресурса на момент конструирования может вообще не быть, но после обращения к Value — он обязательно будет
  • Func<T> — можно сделать себе столько экземпляров ресурса, сколько нужно
  • Func<P...,T> — можно создавать параметризованные экземпляры ресурса.
  • Owned<T> — можно детерминированно очищать неуправляемые ресурсы, требуемые реализацией зависимости прямо или косвенно (уже через свои зависимости)
  • И все остальное что может прийти в голову вам или другому разработчику
Func<T>
— можно сделать себе столько экземпляров ресурса, сколько нужно

Вообще-то тут нет гарантии, что будет много экземпляров а не один. Func единственное что гарантирует, что при вызове вернется объект типа T, а будет ли при каждом вызове новый объект или же каждый раз будет возвращаться один и тот же сказать однозначно нельзя.
Если этот один экземпляр будет вести себя иначе, чем ожидается клиентом от многих, то это нарушение соглашения со стороны компоновщика.
Когда клиент действительно ждет ровно один экземпляр — достаточно Lazy.
А так чисто технически любая фабрика может каждый раз одно и то же возвращать, но клиент ни в коем случае не должен на это закладываться: это не его проблемы, а инжектора.
Клиент не должен закладываться и на то что там каждый раз новый экземпляр тоже. То есть нужно получить экземпляр, использовать его в соответствии с интерфейсом и ничего больше с ним не делать (не освобождать ресурсы, не диспозить и т.п.).
Клиент не должен закладываться и на то что там каждый раз новый экземпляр тоже.

Клиент собственно не то что закладывается, а требует определенную семантику.
Для освобождения ресурсов есть Owned и его эквиваленты.
Для единственного ресурса с отложенным запросом есть Lazy.
Но если в конструкторе указан параметр типа Func, то это подразумевает семантику фабрики, а не синглтона. В частности, если интерфейс включает состояние, то для результатов разных вызовов Func оно должно изменяться независимо. Если просят фабрику, а инжектится синглтон, то это и ответственность, и проблема только того кто инжектит.
Но если в конструкторе указан параметр типа Func, то это подразумевает семантику фабрики, а не синглтона.

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

В норме, за исключением Owned, класс-потребитель не должен делать никаких предположений о жизненном цикле вброшенной в него зависимости.

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

А этот контекст имел изменяемое потребителем состояние? Если не имел, то ваша реализация вполне нормальная, а вот если имел, то получается нарушение инкапсуляции — у потребителя возникали обязательства по работе с контекстом, которые никак не следовали из типа зависимости.
(фабрика, кстати, тоже не обязана отдавать каждый раз новый экземпляр)

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

Я полагаю, не совсем так. Скорее потребитель не обязан делать никаких предположений о зависимости, кроме основанных на ее публичном интерфейсе. Т.е. если Owned — надо грохать ручками, если Func — можно повторно дергать для получения новой реализации. Собственно Func с параметрами делает эту семантику еще более явной.
А этот контекст имел изменяемое потребителем состояние?

Конечно, имел, как и полагается HttpContext.

Но то, что она [фабрика] возвращает, обязано вести себя так, как будто каждый вызов возвращает отдельный экземпляр.

Почему?

если Func — можно повторно дергать для получения новой реализации.

Почему же новой?

Собственно Func с параметрами делает эту семантику еще более явной.

Угу. Autofac (про который вы, как мне кажется, знаете), явно пишет:

However, if you register an object as SingleInstance() and call the Func<X, Y, B> to resolve the object more than once, you will get the same object instance every time regardless of the different parameters you pass in.


Наверное, если бы они не предполагали семантики «создай фабрику, которая возвращает синглтон», они бы запретили ее реализацию?

Конечно, имел, как и полагается HttpContext.

Тогда вы имеете нарушение инкапсуляции. Ибо если получить контекст через делегат дважды, то изменение первого полученного может поменять второй… а может и не поменять. В результате объект-потребитель получает требования от Composition Root, чего быть не должно.
Почему же новой?

Да потому, что если новая реализация не нужна, то можно, не вызывая повторно делегат, просто сохранить старую и пользоваться ей.
Наверное, если бы они не предполагали семантики «создай фабрику, которая возвращает синглтон», они бы запретили ее реализацию?

Я считаю данное конкретное решение ошибкой дизайна отличного DI-контейнера Autofac. Лично мне и моим коллегам по работе оно не раз создавало проблемы и никогда не помогало. Игнорирование переданных параметров во всех вызовах, кроме первого по времени — очень сложно придумать что-нибудь хуже,
В результате объект-потребитель получает требования от Composition Root, чего быть не должно.

Нет, объект-потребитель всегда получает то, что хотел: текущий HttpContext. И если мы в одном поменяли, а другой увидел — значит, они в одном контексте, а если поменяли, а не увидел — значит, в разных. И это — правильное, ожидаемое поведение.

Да потому, что если новая реализация не нужна, то можно, не вызывая повторно делегат, просто сохранить старую и пользоваться ей.

Кроме «старой/новой» еще есть дихотомия «текущий/устаревший», и вот она интереснее.

Понимаете, объект за псевдо-фабрикой — это не обязательно сервис, это может быть контекст (а у них другое поведение), это может быть сущность (у них тоже свое поведение). Например, за «фабрикой» может быть identity map, который на каждый вариант параметра возвращает свой объект, но далее для того же варианта будет тот же объект.
Нет, объект-потребитель всегда получает то, что хотел: текущий HttpContext.

А, вот оно в чем дело. Но тогда лучше не Func, а IObservable или IChangeable какой-нибудь — здесь, в отличие от делегата, и семантика доступа именно к текущему контексту понятна, и момент смены контекста наблюдаем.
Понимаете, объект за псевдо-фабрикой — это не обязательно сервис, это может быть контекст (а у них другое поведение), это может быть сущность (у них тоже свое поведение). Например, за «фабрикой» может быть identity map, который на каждый вариант параметра возвращает свой объект, но далее для того же варианта будет тот же объект.

Это я с самого начала говорил: за Func может стоять что угодно, но обязанное обеспечить семантику фабрики. Т.е. если между параметризованными объектами с одинаковыми параметрами разницы нет никакой, то да, кеширование рулит.
А, вот оно в чем дело. Но тогда лучше не Func, а IObservable или IChangeable какой-нибудь — здесь, в отличие от делегата, и семантика доступа именно к текущему контексту понятна, и момент смены контекста наблюдаем.

А мне не надо видеть момент смены, мне достаточно получить текущий.

Это я с самого начала говорил: за Func может стоять что угодно, но обязанное обеспечить семантику фабрики.

Для контекстов нет семантики фабрики. Даже для доставания объектов из реестра нет семантики фабрики.
А мне не надо видеть момент смены, мне достаточно получить текущий.

Для этого тем более Func не нужен.
Для контекстов нет семантики фабрики. Даже для доставания объектов из реестра нет семантики фабрики.

И Func в обоих случаях оказывается лишним.
Для этого тем более Func не нужен.

И Func в обоих случаях оказывается лишним.

А как мне это сделать без Func?

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

Вот с этого места поподробнее, пожалуйста.
Я для «переключаемых» сущностей использую вот это
public interface IChangeable<T>
{
    T Value { get; }
    IObservable<T> Changed { get; }
}

И никакого Func тут не нужно от слова совсем.
То есть наименование типа объекта должно говорить что это

Так Func и есть наименование типа, которое ясно и четко говорит что это фабрика (семантически).
В вашей трактовке помимо Func надо смотреть еще и на тип-параметр, что усложняет и реализацию, и использование.
Вдобавок это противоречит самому понятию generic, так как ваш Func имеет разную семантику для разных типов-параметров.
Так Func и есть наименование типа, которое ясно и четко говорит что это фабрика (семантически).
В вашей трактовке помимо Func надо смотреть еще и на тип-параметр, что усложняет и реализацию, и использование.
Вдобавок это противоречит самому понятию generic, так как ваш Func имеет разную семантику для разных типов-параметров.

Простите, но Func это семантически не фабрика, а функтор. И не надо в него вкладывать семантику, которой в нем нет.
Генерик параметр в нем говорит что этот функтор возвращает объект такого типа. Больше он ничего семантически не гарантирует.
В принципе да, логично завести аналогичный тип Factory и использовать его вместо Func.
Но конкретно для разрешения зависимостей нет ничего плохого в том, чтобы подразумевать фабрику.
Иначе для каждого конкретного случая приходится вводить дополнительные ad-hoc требования. Здесь переключаем, здесь создаем, здесь отдаем одно и то же всегда и т.п., а Func «вообще» снова не при делах.
Вот это вот
если Func — можно повторно дергать для получения новой реализации
непонятно откуда взялось. На основании чего вы вкладываете в делегат семантику отдачи каждый раз нового объекта мне не понятно.
Нет. Func не закладывает такой семантики. Равно как и Lazy не закладывает семантики singleton, на самом деле. Просто класс потребитель сервиса не должен ожидать от поставщика этого сервиса никакого поведения, отличного от описанного контрактом (интерфейсом). То есть он не должен вообще думать о внутреннем состоянии сервиса, который ему сгенерировала фабрика. Все о чем он должен думать — это что сервис выполняет обещанные контрактом операции.
Func не закладывает такой семантики.

Без такой семантики Func просто не нужен по построению.
Равно как и Lazy не закладывает семантики singleton, на самом деле.

Разные обращения к свойству Value могут вернуть разные (семантически) объекты? На мой взгляд это антипаттерн, усложняющий жизнь по обе стороны компонента.
То есть он не должен вообще думать о внутреннем состоянии сервиса, который ему сгенерировала фабрика. Все о чем он должен думать — это что сервис выполняет обещанные контрактом операции.

Я не про внутреннее состояние, а вовсе даже про внешнее. Когда интерфейс имеет изменяемое состояние, то разница между возвратом нового и старого экземпляров при каждом вызове Func имеет значение и от этого никуда не деться. В примере с http-контекстом, если сделать два вызова делегата и начать менять состояние обоих полученных контекстов, разница между семантикой синглтона и фабрики всплывет на первом же юнит-тесте.
Без такой семантики Func просто не нужен по построению.


Чего это не нужен? В этом случае мы перекладываем ответственность по управлению инстансами нашего сервиса на эту фабрику. И явно говорим, что нам все равно какой экземпляр нам будет возвращен каждый раз когда мы вызовем фабрику.
То есть мы не привязываем наш класс потребитель к конкретному экземпляру который вбросили в конструктор, а получаем в каждом месте вызова фабрики тот экземпляр, который DI контейнер посчитает нужным нам отдать. А DI кнтейнер пусть управляет временем жизни этого экземпляра так как он настроен.

Я не про внутреннее состояние, а вовсе даже про внешнее. Когда интерфейс имеет изменяемое состояние, то разница между возвратом нового и старого экземпляров при каждом вызове Func имеет значение и от этого никуда не деться. В примере с http-контекстом, если сделать два вызова делегата и начать менять состояние обоих полученных контекстов, разница между семантикой синглтона и фабрики всплывет на первом же юнит-тесте.


А это определяется семантикой которую в себе несет объект. Если он (его название) содержит в себе семантику контекста — он должен сохранять внешнее состояние до смены контекста. Если у него в семантике заложен Cache, соответственно должен вести себя как кеш и т.п.
У вас Func<TВася> и Func<TПетя> имеют разную семантику?
Это явно не то, что можно ожидать от любого обобщенного типа, включая Func
Func<TВася> и Func<TПетя> имеют одинаковую семантику. Оба они возвращают объект типа, соответствующего генерик параметру. Ничего сверх того они не обещают.
Вас же не удивляет, что Func<T,bool> и Func<T,string> могут иметь разную семантику? Более того, что тот же Func<T,bool> в Where и CheckBoxFor имеет разную семантику?
Там оно в разрешении зависимостей не участвует и семантика определяется самим методом (Where, CheckBoxFor)
Паттерн DI, напротив, тяготеет к отделению семантики параметров-зависимостей от конкретного конструктора.
Да, вы можете и обойтись и без этого, но с зависимостями, чья семантика однозначно определяется типом, работать ИМХО проще.
Да, вы можете и обойтись и без этого, но с зависимостями, чья семантика однозначно определяется типом, работать ИМХО проще.

Семантика в первую очередь определяется типом самой зависимости, а не вторичного отношения (Func, Owned и т.д.), построенного вокруг нее.
Семантика в первую очередь определяется типом самой зависимости

Я предпочитаю для комбинированных типов считать все исходные части комбинации независимыми друг от друга без деления на первичное и вторичное. В вашей трактовке «основной» тип влияет еще и на семантику «вторичного». Это позволяет всегда писать Func вместо типов с конкретной семантикой, но ИМХО затрудняет чтение кода и все равно требует дополнительных «вторичных» типов, если требуется поведение, отличное от заданного для «первичного» типа по умолчанию.
Я, если мне нужна конкретная семантика завожу специальный тип, а вот используя Func никакой дополнительной семантики не влкадываю.
Собственно, нет никакого нарушения инкапсуляции в транзитивной зависимости. А полностью избавиться от всех зависимостей не получится даже теоретически, потому и используются интерфейсы, чтобы только минимизировать эту зависимость.

Возможно, полностью приблизиться к идеалу можно только в динамично-типизированных языках с обменом «сообщений», как, например, в smalltalk, но это уже не тема топика.
Избавиться от транзитивных зависимостей можно, например, с помощью Command Bus.

Чем тут поможет динамическая типизация, я не понял.
Рассмотрим WPF-app с MVVM в качестве UI-паттерна.
Два вопроса:
1. Как вы считаете, если ViewModel требует в конструкторе две-три зависимости и сама их не использует, а только конструирует Model с помощью них, имеется ли здесь проблема транзитивных зависимостей?
2. Если на первый вопрос ответ положительный, то что такое Command Bus и как это поможет в таком случае?
Конкретно WPF я ни разу в жизни не видел (ой, это .net хаб же!), но по паттернам понятно, о чем речь.
Нет, в данном случае не думаю, что это проблема. Я скорее имел ввиду application/domain model layers.
Ясно. Где порекомендуете почитать про Command Bus? Или просто погуглить?
В принципе, погуглить — достаточно, вроде на первой странице выдача приличная.

Наиболее интересно это все выглядит при использовании совместно с CQRS. Тут могу порекомендовать — тем более, раз уж мы в дотнет-хабе — Microsoft-овскую книгу «Exploring CQRS and Event Sourcing» [1]: несмотря на обильную рекламу Azure, тема вполне неплохо раскрыта :)

[1] http://www.microsoft.com/en-us/download/details.aspx?id=34774
Избавиться от транзитивных зависимостей можно с помощью инъекции фабрики. Зачем ради такой мелочи тащить Command Bus?
Да, в ряде очевидных случаев фабрики более чем достаточно. Но в таких случаях обычно такой вопрос и не поднимается.
Да в принципе заголовка статьи достаточно было)
В Java шаблон ServiceLocator (java.util.ServiceLoader) используется очень часто, когда нужно подключать различные имплементации публичного API в зависимости от classpath.
С одной стороны это универсальный способ без лишних заморочек использовать различные сервис-провайдеры. Проблемы начинаются когда нужно сконфигурировать конкретный провайдер, ибо для этого не придумано хорошего стандартного способа. Часто это делается либо через system properties, либо через внешний файл конфигурации.
Sign up to leave a comment.

Articles