I've been working on refactoring an application to make it more manageable with dependency injection and all that good stuff. When doing this I have encountered cyclic dependencies more than once.
So here is a typical example of cyclic dpendency:
interface IA
{
int Data { get; }
}
interface IBefore
{
void DoStuffBefore();
}
class A: IA
{
public int Data { get; private set; }
IBefore before;
public A(IBefore before)
{
this.before = before;
}
public void Increment()
{
before.DoStuffBefore();
Data++;
}
}
class B: IBefore
{
IA a;
public B(IA a)
{
this.a = a;
}
public void WriteADataToConsole()
{
Console.Write(a.Data);
}
public void DoStuffBefore() //From IBefore
{
WriteADataToConsole();
}
}
I cannot create neither of the classes since they require eachother. Now the standard(?) thing to do in this case would be to separate A's data from A:
public interface IA
{
int Data { get; set; }
}
public interface IBefore
{
void DoStuffBefore();
}
class AData : IA
{
public int Data { get; set; }
}
class A
{
public IA Data { get; private set; }
IBefore before;
public A(IA data, IBefore before)
{
this.Data = data;
this.before = before;
}
public void Increment()
{
before.DoStuffBefore();
Data.Data++;
}
}
class B : IBefore
{
IA a;
public B(IA a)
{
this.a = a;
}
public void WriteADataToConsole()
{
Console.Write(a.Data);
}
public void DoStuffBefore() //From IBefore
{
WriteADataToConsole();
}
}
The above solves the circular dpendency because I can now create AData first and then inject it into B and inject B into A. But I could also put an event i IA that B can listen to:
public interface IA
{
int Data { get; }
event Action BeforeEvent;
}
class A: IA
{
public int Data { get; private set; }
public event Action BeforeEvent;
public void Increment()
{
BeforeEvent();
Data++;
}
}
class B
{
IA a;
public B(IA a)
{
this.a = a;
a.BeforeEvent += new Action(WriteADataToConsole);
}
void WriteADataToConsole() //Event listener
{
Console.Write(a.Data);
}
}
This is sonething I stumbled upon because I was trying to convert the event approach to dependency injection and realised that by doing so I had gotten myself a circular dependency.
Some of the questions that's been troubleing my brain are:
Good question! In 95% cases you have to either merge this two entities together or break dependency some other way, but… What if you can't for one reason or another merge'em into one entity (working with UI sometimes could be such tricky)? There is a book about "Dependency Injection in .NET" by Mark Seemann where described two approaches to break cyclic dependencies:
In your 2nd implementation with properties there is a constructor: public A(IA data, IBefore before)
. Both IA data
and IBefore before
are required in terms of dependency injection — here is a best point to breack cicle! Here is an implemnetation with optional IBefore
:
class A
{
public IA Data { get; private set; }
public IBefore Before { get; set; }
public A(IA data)
{
this.Data = data;
}
public void Increment()
{
// here should be design decision: if Before is optional…
if(Before == null)
{
Before.DoStuffBefore();
}
// …or required
if(Before == null)
{
throw new Exception("'Before' is required");
}
Data.Data++;
}
}
It up to you, either to skip Before.DoStuffBefore()
call if Before
is optional, or raise an exception if it's required
According to your questions:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With