Rickard
Rickard

Reputation: 100

Dependency injection vs events and resolving cyclic dependencies with events

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:

Upvotes: 8

Views: 4727

Answers (1)

Akim
Akim

Reputation: 8679

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:

  • Events — preferable way according to DI book and you already did this. It's looks fine for me
  • Property Injection — in contrast to Constructor Injection, Property Injection means that injected resourse is optional.

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:

  • Which would be considered the best design? What would be some guidelines? Pros and cons — imho both are ok. Event is more general. Properties is more easy to implement and to handle
  • Events are always preferrable when void is returned? — Yes for me

Upvotes: 4

Related Questions