Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve a circular dependency while still using Dagger2?

Tags:

I have two classes, Foo<T> and Bar, which depend on each other, as well as various other classes. I am using Dagger-2 for dependency injection, but if I naively add the circular dependency, Dagger hits a stack overflow at runtime. What's a good way to refactor the classes to fix this, while still using Dagger to inject all the other dependencies, and with minimal duplication and changes to existing calls?

like image 658
Jesse W at Z - Given up on SE Avatar asked Jun 22 '17 21:06

Jesse W at Z - Given up on SE


People also ask

How can circular dependencies be avoided?

Circular dependencies can be introduced when implementing callback functionality. This can be avoided by applying design patterns like the observer pattern.

How can Spring circular dependency be prevented?

4.2. A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it's first needed.

Is it okay to have circular dependency?

and, yes, cyclic dependencies are bad: They cause programs to include unnecessary functionality because things are dragged in which aren't needed. They make it a lot harder to test software. They make it a lot harder to reason about software.


2 Answers

The easy way out is to use Lazy<T> on one side.

Lazy<Foo> foo;  @Inject Bar(Lazy<Foo> foo) {     this.foo = foo; }  // use foo.get(); when needed 
like image 117
EpicPandaForce Avatar answered Sep 22 '22 18:09

EpicPandaForce


After an excessive amount of thought and talks with coworkers, we ended up doing the following:

class Foo<T> extends FooWithoutDep<T> {     @Inject Foo(Bar bar, OtherDep1 dep1, OtherDep2 dep2) {         super(dep1, dep2);         setBarDep(bar);     } }  class FooWithoutDep<T> {     //Field declarations elided     @Inject FooWithoutDep(OtherDep1 dep1, OtherDep2 dep2) {         //Normal constructor stuff     }     void setBarDep(Bar bar) { this.bar = bar; }      //The rest of the actual logic }  class Bar {     //Field declarations elided     @Inject Bar(FooWithoutDep<Thing> foo, OtherDep3 dep3) {         this.foo = foo;         this.foo.setBarDep(this);         this.dep3 = dep3;     }      //Code that uses Foo and the other dependencies } 

Explaining this -- we moved the actual logic of Foo into a parent class (FooWithoutDep), that took the circular dependency as a settable field rather than a constructor parameter. Then the original class just contained a constructor that took the circular dependency and called the setter. The other class, Bar, depended on the parent (FooWithoutDep), and called the setter explicitly, passing itself (this). This enables all the existing references to the class to remain the same, while still using Dagger to inject all the dependencies.

This seemed confusing enough to be worth writing up here.

like image 28
Jesse W at Z - Given up on SE Avatar answered Sep 20 '22 18:09

Jesse W at Z - Given up on SE