Как решить круговую зависимость
Привет, у меня проблема со структурой моего кода, он как-то переходит в круговую зависимость. Вот объяснение того, как выглядит мой код:
У меня есть ProjectA содержит BaseProcessor и BaseProcessor имеет ссылку на класс под названием Структура в ProjectB. Внутри BaseProcessor есть экземпляр Structure как переменная.
В projectB есть некоторые другие классы, такие как Pricing, Transaction и т. Д. Каждый класс в ProjectB имеет базовый класс с именем BaseStructure, то есть классы Structure, Pricing и Transaction, унаследованные от BaseStructure. Теперь в классах Pricing и Transaction я хочу вызвать метод из класса BaseProcessor из класса BaseStructure, который вызывает циклическую зависимость.
Что я пробовал это:
Используя Unity, но я не выяснил, как заставить его работать, потому что я пытаюсь использовать функцию вроде:
unityContainer.ReferenceType(IBaseProcessor, BaseProcessor) в BaseStructure, тогда ему потребуется ссылка на BaseProcessor, которая также вызывает циклическую зависимость.
И я также попытался создать интерфейс IBaseProcessor и создать объявление функции (функцию, которую я хочу вызвать) в этом интерфейсе. И пусть и BaseProcessor, и BaseStructure наследуют этот интерфейс. Но как я могу вызвать функцию в классе Pricing and Transaction без создания экземпляра BaseProcessor?
Может кто-нибудь сказать, пожалуйста, как решить эту проблему, кроме использования отражения?
Любая помощь будет высоко ценится. Спасибо:)
2 ответа
Вы можете использовать ленивое разрешение:
public class Pricing {
private Lazy<BaseProcessor> proc;
public Pricing(Lazy<BaseProcessor> proc) {
this.proc = proc;
}
void Foo() {
this.proc.Value.DoSomethin();
}
}
Обратите внимание, что вам не нужно регистрировать Lazy, потому что Unity разрешит его при регистрации BaseProcessor.
Ваш DI-контейнер не может помочь в решении циклической ссылки, поскольку именно структура зависимостей приложения препятствует созданию объектов. Даже без DI-контейнера вы не можете создавать свои графы объектов без каких-либо специальных "уловок".
Обратите внимание, что в большинстве случаев графы циклических зависимостей являются признаком недостатка проекта в вашем приложении, поэтому вы можете рассмотреть возможность более внимательного изучения вашего проекта и выяснения, не может ли это быть решено путем выделения логики в отдельные классы.
Но если это не вариант, есть два способа разрешения этого графа циклических зависимостей. Либо вам нужно вернуться к внедрению свойства, либо отложить разрешение компонента на заводе, Func<T>
или как @onof, предложенный с Lazy<T>
,
В этих двух вариантах есть много возможных способов сделать это, например, вернуться к внедрению свойств в ваше приложение (извините, мой C#):
public class BaseStructure {
public BaseStructure(IDependency d1) { ... }
// Break the dependency cycle using a property
public IBaseProcessor Processor { get; set; }
}
Это перемещает IBaseProcessor
зависимость от конструктора к свойству и позволяет установить его после построения графа. Вот пример графа объектов, который строится вручную:
var structure = new Structure(new SomeDependency());
var processor = new BaseProcessor(structure);
// Set the property after the graph has been constructed.
structure.Processor = processor;
Лучший вариант - скрыть свойство внутри корня композиции. Это делает ваш дизайн приложения чище, так как вы можете продолжать использовать инжектор конструктора. Пример:
public class BaseStructure {
// vanilla constructor injection here
public BaseStructure(IDependency d1, IBaseProcessor processor) { ... }
}
// Defined inside your Composition Root.
private class CyclicDependencyBreakingProcessor : IBaseProcessor {
public IBaseProcessor WrappedProcessor { get; set; }
void IBaseProcessor.TheMethod() {
// forward the call to the real processor.
this.WrappedProcessor.TheMethod();
}
}
Теперь вместо инъекций BaseProcessor
в ваш Structure
вводишь CyclicDependencyBreakingProcessor
, который будет дополнительно инициализирован после построения графика:
var cyclicBreaker = new CyclicDependencyBreakingProcessor();
var processor = new BaseProcessor(new Structure(new SomeDependency(), cyclicBreaker));
// Set the property after the graph has been constructed.
cyclicBreaker.WrappedProcessor = processor;
В основном это то же самое, что и раньше, но теперь приложение не замечает того факта, что существует циклическая зависимость, которую нужно было сломать.
Вместо внедрения свойства вы также можете использовать Lazy<T>
, но так же, как и при внедрении свойства, лучше скрыть эту деталь реализации внутри корня композиции и не допускать Lazy<T>
значения просачиваются в ваше приложение, поскольку это просто добавляет шум в ваше приложение, что делает ваш код более сложным и трудным для тестирования. Кроме того, приложение не должно заботиться о том, что внедрение зависимостей задерживается. Так же, как с Func<T>
(а также IEnumerable<T>
) при введении Lazy<T>
зависимость определяется с учетом конкретной реализации, и мы просочились в детали реализации. Так что лучше сделать следующее:
public class BaseStructure {
// vanilla constructor injection here
public BaseStructure(IDependency d1, IBaseProcessor processor) { ... }
}
// Defined inside your Composition Root.
public class CyclicDependencyBreakingProcessor : IBaseProcessor {
public CyclicDependencyBreakingBaseProcessor(Lazy<IBaseProcessor> processor) {...}
void IBaseProcessor.TheMethod() {
this.processor.Value.TheMethod();
}
}
Со следующей проводкой:
IBaseProcessor value = null;
var cyclicBreaker = new CyclicDependencyBreakingProcessor(
new Lazy<IBaseProcessor>(() => value));
var processor = new BaseProcessor(new Structure(new SomeDependency(), cyclicBreaker));
// Set the value after the graph has been constructed.
value = processor;
До сих пор я только показывал, как построить граф объектов вручную. Делая это с использованием DI-контейнера, вы обычно хотите, чтобы DI-контейнер создавал для вас полный граф, поскольку это дает более поддерживаемый корень композиции. Но это может усложнить нарушение циклических зависимостей. В большинстве случаев трюк состоит в том, чтобы зарегистрировать компонент, который вы хотите разорвать с кэширующим образом жизни (в основном все, что угодно, кроме временного). Например, для веб-запроса. Это позволяет вам получить тот же экземпляр ленивым способом.
Используя последний CyclicDependencyBreakingProcessor
Например, мы можем создать следующую регистрацию Unity:
container.Register<BaseProcessor>(new PerRequestLifetimeManager());
container.RegisterType<IStructure, Structure>();
container.RegisterType<IDependency, SomeDependenc>();
container.Register<IBaseProcessor>(new InjectionFactory(c =>
new CyclicDependencyBreakingProcessor(
new Lazy<IBaseProcessor>(() => c.GetInstance<BaseProcessor>())));