Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • Both solutions solve the circular dependency(right?) and as far as I can see they open up for extension of A to an equal degree, but which would be considered the best design?
  • What would be some guidelines for when to use events and when to use DI to solve circular dependencies and in general?
  • Obviously events are not good if A would need a return value from B. Does that mean that events are always preferrable when void is returned?
  • What are the pros and cons of each solution?
like image 878
Rickard Avatar asked Oct 22 '22 22:10

Rickard


1 Answers

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
like image 147
Akim Avatar answered Oct 24 '22 11:10

Akim