How to Solve Circular Dependency

2019-07-18 10:30发布

Hi I have a problem with the structure of my code, it somehow goes into Circular Dependency. Here is an explanation of how my code looks like:

I have a ProjectA contains BaseProcessor and BaseProcessor has a reference to a class called Structure in ProjectB. Inside BaseProcessor, there is an instance of Structure as a variable.

In projectB there are someother classes such as Pricing, Transaction etc. Every class in ProjectB has a base class called BaseStructure i.e. Structure, Pricing and Transaction classes all inherited from BaseStructure. Now in Pricing and Transaction classes, I want to call a method in BaseProcessor class from BaseStructure class which causing Circular Dependency.

What I have tried is:
Using Unity, but I didn't figure out how to make it work because I try to use function like: unityContainer.ReferenceType(IBaseProcessor, BaseProcessor) in BaseStructure then it will need a reference of BaseProcessor which also cause Circular Dependency.

And I've also tried creating an interface of IBaseProcessor and create a function(the function I want to call) declaration in this interface. And let both BaseProcessor and BaseStructure inherit this interface. But how can I call the function in Pricing and Transaction class without create an instance of BaseProcessor?

Can anyone please tell me how to resolve this problem other than using reflection?

Any help will be much appreciated. Thanks :)

2条回答
太酷不给撩
2楼-- · 2019-07-18 11:07

You could use the lazy resolution:

public class Pricing {
   private Lazy<BaseProcessor> proc;

   public Pricing(Lazy<BaseProcessor> proc) {
      this.proc = proc;
   }

   void Foo() {
      this.proc.Value.DoSomethin();
   }
}

Note that you haven't to register the Lazy because Unity will resolve it by BaseProcessor registration.

查看更多
3楼-- · 2019-07-18 11:14

Your DI container can't help solving the circular reference, since it is the dependency structure of the application that prevents objects from being created. Even without a DI container, you can't construct your object graphs without some special 'tricks'.

Do note that in most cases cyclic dependency graphs are a sign of a design flaw in your application, so you might want to consider taking a very close look at your design and see if this can't be solved by extracting logic into separate classes.

But if this is not an option, there are basically two ways of resolving this cyclic dependency graph. Either you need to fallback to property injection, or need to postpone resolving the component with a factory, Func<T>, or like @onof proposed with a Lazy<T>.

Within these two flavors, there are a lot of possible ways to do this, for instance by falling back to property injection into your application (excuse my C#):

public class BaseStructure {
    public BaseStructure(IDependency d1) { ... }

    // Break the dependency cycle using a property
    public IBaseProcessor Processor { get; set; }
}

This moves the IBaseProcessor dependency from the constructor to a property and allows you to set it after the graph is constructed. Here's an example of an object graph that is built manually:

var structure = new Structure(new SomeDependency());
var processor = new BaseProcessor(structure);

// Set the property after the graph has been constructed.
structure.Processor = processor;

A better option is to hide the property inside your Composition Root. This makes your application design cleaner, since you can keep using constructor injection. Example:

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

Now instead of injecting the BaseProcessor into your Structure, you inject the CyclicDependencyBreakingProcessor, which will be further initialized after the construction of the graph:

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;

This is basically the same as before, but now the application stays oblivious from the fact that there is a cyclic dependency that needed to be broken.

Instead of using property injection, you can also use Lazy<T>, but just as with the property injection, it is best to hide this implementation detail inside your Composition Root, and don't let Lazy<T> values leak into your application, since this just adds noise to your application, which makes your code more complex and harder to test. Besides, the application shouldn't care that the dependency injection is delayed. Just as with Func<T> (and IEnumerable<T>), when injecting a Lazy<T> the dependency is defined with a particular implementation in mind and we're leaking implementation details. So it's better to do the following:

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

With the following wiring:

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;   

Up until now I only showed how to build up the object graph manually. When doing this using a DI container, you usually want to let the DI container build up the complete graph for you, since this yields a more maintainable Composition Root. But this can make it a bit more tricky to break the cyclic dependencies. In most cases the trick is to register the component that you want to break with a caching lifestyle (basically anything else than transient). Per Web Request Lifestyle for instance. This allows you to get the same instance in a lazy fashion.

Using the last CyclicDependencyBreakingProcessor example, we can create the following Unity registration:

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>())));
查看更多
登录 后发表回答