Как решить круговую зависимость

Привет, у меня проблема со структурой моего кода, он как-то переходит в круговую зависимость. Вот объяснение того, как выглядит мой код:

У меня есть 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>())));
Другие вопросы по тегам