2009-12-12 5 views
8

Довольно новая инъекция зависимостей, и я пытаюсь выяснить, является ли это анти-шаблоном.Инъекция инжектора зависимостей с использованием инъекции зависимостей

Скажем, у меня есть 3 сборки:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.Users необходим объект, который построен в Foo.Payment и Foo.Payment также нуждается в вещи из Foo.Users. Это создает некоторую циклическую зависимость.

Я определил интерфейс в Foo.Shared, который проксирует структуру Injection Dependency, которую я использую (в данном случае NInject).

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

В приложении контейнера, у меня есть реализация этого интерфейса:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

конфигурация выглядит следующим образом:

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

Это позволяет мне создавать новый объект Foo.Payment.SomeType изнутри Foo.Users без прямой ссылки:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

Непонятно, какие именно точные зависимости класса UserAccounts в этом случае заставляют меня думать, что это не очень хорошая практика.

Как еще я могу это сделать?

Любые мысли?

+1

+1 только для названия языка твистер. – womp

+0

То же самое здесь, мне нравится название :) –

ответ

7

Хотя несколько спорных: да, это анти-шаблон. Он известен как Service Locator, и, хотя некоторые считают его правильным шаблоном проектирования, я считаю его анти-шаблоном.

Эта проблема заключается в том, что использование, например, ваш класс UserAccounts становится неявным вместо явно. Хотя конструктор утверждает, что ему нужен IDependencyResolver, он не указывает, что ему следует делать. Если вы передадите ему IDependencyResolver, который не может разрешить ISomeType, он собирается бросить.

Хуже то, что при последующих итерациях может возникнуть соблазн разрешить некоторые другого типа из UserAccounts. Он собирается компилироваться просто отлично, но, скорее всего, будет выполняться во время выполнения, если/когда тип не может быть разрешен.

Не ходите по этому маршруту.

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

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

  • Во многих случаях Публикация/подписка модель хорошо работает.
  • Посредник образец может предоставить альтернативу, если сообщение должно идти в обе стороны.
  • Вы также можете ввести Abstract Factory, чтобы получить экземпляр, который вам нужен по мере необходимости, вместо того, чтобы немедленно его подключать.
+0

Вот что я думал, что зависимости не ясны, поэтому это должно быть антипаттерн. :) Абстрактной фабрикой был мой первый выбор, но он перепробовал код. В реальном приложении мне нужно создать множество отдельных типов, а не только одно. Я либо жестко программирую каждый другой заводской метод, либо использую generics для связывания интерфейса с конкретным классом (также жестко запрограммированным). Но я теряю силу инфраструктуры инъекций зависимостей таким образом, и станет действительно беспорядочно настраивать привязки, даже до того, как нужно использовать ручную зависимость ввода/типа кода распознавателя, используя отражение. – andreialecu

+0

Всегда есть цена, которую нужно заплатить :) Я не согласен с тем, что настройка контейнера становится беспорядочной - она ​​может стать довольно длинной и многословной, но она будет состоять из почти почти декларативного кода, который является отличный компромисс. Большая часть долговечности, которую вы можете разрешить с помощью конфигурации, основанной на соглашениях, - особенно, если в итоге вы получаете множество аналогичных абстрактных фабрик, которые должны быть настроены одинаково. Castle Windsor имеет функциональность, которая позволяет настраивать по соглашению в нескольких простых операциях - я не знаю, может ли NInject это сделать ... –

1

Это кажется немного странным для меня. Можно ли отделить логику, которая нуждается в обеих ссылках, в третью сборку, чтобы разбить зависимости и избежать риска?

2

Я согласен с ForeverDebugging - было бы хорошо устранить круговую зависимость. Смотрите, если вы можете отделить классы, как это:

  • Foo.Payment.dll: Классы, которые имеют дело только с оплатой, а не с пользователями
  • Foo.Users.dll: Классы, которые имеют дело только с пользователями, а не с оплаты
  • Foo.UserPayment.dll: Классы, которые имеют дело как с оплатой и пользователи

Тогда у вас есть один узел, который ссылается на два других, но не круг зависимостей.

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

  • Foo.Users.UserAccounts зависит от Foo.Shared.IPaymentHistory, которая реализуется Foo.Payment.PaymentHistory.
  • Другой класс оплаты, Foo.Payment.PaymentGateway, зависит от Foo.Shared.IUserAccounts. IUserAccounts реализуется с помощью Foo.Users.UserAccounts.

Предположим, что других зависимостей нет.

Здесь есть круг сборок, которые будут зависеть друг от друга во время выполнения приложения (хотя они не зависят друг от друга во время компиляции, поскольку они проходят через общую DLL). Но нет круга классов, которые зависят друг от друга, во время компиляции или во время выполнения.

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



Если вы в конечном итоге с круговой зависимости между классами, вы, вероятно, необходимо использовать инъекции собственности (ака сеттер инъекции) на одном из классов, вместо инъекции конструктора. Я не использую Ninject, но он поддерживает вложение свойств - here is the documentation.

Обычно контейнеры IoC используют инъекцию конструктора - они передают зависимости в конструктор класса, который зависит от них. Но это не работает, когда есть круговая зависимость. Если классы A и B зависят друг от друга, вам нужно передать экземпляр класса A в конструктор класса B. Но для того, чтобы создать A, вам нужно передать экземпляр класса B в его конструктор. Это проблема с курицей и яйцом.

С введением свойств вы указываете свой контейнер IoC, чтобы сначала вызвать конструктор, а затем установить свойство на построенный объект. Обычно это используется для дополнительных зависимостей, таких как регистраторы. Но вы также можете использовать его для разрыва круговой зависимости между двумя классами, которые требуют друг друга.

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