Breaking Circular Dependency Between Components

Introduction

A couple of days ago, I came across a piece of code that was not just tightly coupled but also formed a circular dependency among the components. In this article, I've tried to give you the gist of it (it may not be the best though) along with a solution to break the dependency. The below code has two key components, DataProvider and DataValidator. The DataProvider "new" is DataValidator and passes in its own instance, which is then used to fetch data, making them very tightly coupled to each other.
  1. public class DataValidator  
  2. {  
  3.     private DataProvider _dataProvider;  
  4.     public DataValidator(DataProvider dataProvider)  
  5.     {  
  6.         _dataProvider = dataProvider;  
  7.     }  
  8.       
  9.     public bool ValidateData()  
  10.     {  
  11.         var dataReceived = _dataProvider.GetData();  
  12.         return dataReceived > 0 ? true : false;  
  13.     }  
  14. }  
  15.   
  16. public class DataProvider  
  17. {     
  18.     public bool ValidateData()  
  19.     {  
  20.         return new DataValidator(this).ValidateData();  
  21.     }  
  22.       
  23.     public bool ProcessData()  
  24.     {  
  25.         // some data processing here  
  26.         return true;  
  27.     }  
  28.       
  29.     public int GetData()  
  30.     {  
  31.         // return some raw data  
  32.         return 1;  
  33.     }  
  34. }  
  35.   
  36. public class Program  
  37. {  
  38.     public static void Main()  
  39.     {  
  40.         var dataProvider = new DataProvider();  
  41.         bool success = dataProvider.ValidateData();  
  42.         if(success)  
  43.         {  
  44.             dataProvider.ProcessData();  
  45.             Console.WriteLine("success");  
  46.         }  
  47.         else  
  48.         {
  49.             Console.WriteLine("error");  
  50.         }
  51.     }  
  52. }  
Circular Dependency Problem
 
In the above code sample, DataProvider needs DataValidator to perform some data validation. However, the latter, in turn, needs the DataProvider to retrieve that data. This will work fine unless DataValidator needs one or more additional components. This is to say that, if we are to add one more dependency in DataValidator then we will have to wire it up to the hierarchy which is DataProvider.
 
Imagine, that the service performing the validations now wants to log all the errors and for that, it has a dependency on ILogger. In that case, if we are to add that dependency, the components will look like this, 
  1. public class DataValidator  
  2. {  
  3.     private DataProvider _dataProvider;  
  4.     private ILogger _logger;  
  5.   
  6.     public DataValidator(DataProvider dataProvider, ILogger logger)  
  7.     {  
  8.         _dataProvider = dataProvider;  
  9.         _logger = logger;  
  10.     }  
  11.   
  12.     public bool ValidateData()  
  13.     {  
  14.         var dataReceived = _dataProvider.GetData();  
  15.         var validData = dataReceived > 0 ? true : false;  
  16.   
  17.         if (!validData)  
  18.             _logger.Log("Invalid data received.");  
  19.   
  20.         return validData;  
  21.     }  
  22. }  
  23.   
  24. public class DataProvider  
  25. {     
  26.     ...  
  27.     public bool ValidateData()  
  28.     {  
  29.         var dataValidator = new DataValidator(thisnew Logger());  
  30.         return dataValidator.ValidateData();  
  31.     }  
  32.     ...  
  33. }  
Because the two components are tightly wired, the dependency on ILogger has to be injected up the hierarchy as: 
  1. var dataValidator = new DataValidator(thisnew Logger());  
As a result, the DataProvider has an additional responsibility of instantiating ILogger and providing it to DataValidator, while creating its instance.

The Solution
 
Since the two components need to talk to each other, there has to be a way to do so without having a circular dependency. Surprisingly, there is a way - using EventHandler. The proposal is, that DataValidator will raise the event "DataRequired", prior to performing any data validation operation. DataProvider, on the other hand, will subscribe to this event and will respond by providing the data.
 
The following code shows the use of EventHandler, in order to break the circular dependency, 
  1. public class DataValidator  
  2. {  
  3.     private ILogger _logger;  
  4.   
  5.     public event EventHandler DataRequired;  
  6.     public int Data { getset; }  
  7.   
  8.     public DataValidator(ILogger logger)  
  9.     {  
  10.         _logger = logger;  
  11.     }  
  12.   
  13.     public bool ValidateData()  
  14.     {  
  15.         DataRequired?.Invoke(this, EventArgs.Empty);  
  16.         return Data > 0 ? true : false;  
  17.     }  
  18. }  
  19.   
  20. public class DataProvider  
  21. {  
  22.     private DataValidator _dataValidator;  
  23.     public DataProvider(DataValidator dataValidator)  
  24.     {  
  25.         _dataValidator = dataValidator;  
  26.         _dataValidator.DataRequired += GetData;  
  27.     }  
  28.   
  29.     public bool ValidateData()  
  30.     {  
  31.         return _dataValidator.ValidateData();  
  32.     }  
  33.   
  34.     public bool ProcessData()  
  35.     {  
  36.         // some data processing here  
  37.         return true;  
  38.     }  
  39.   
  40.     public void GetData(object sender, EventArgs e)  
  41.     {  
  42.         // set Data property in DataValidator  
  43.         _dataValidator.Data = 1;  
  44.     }  
  45. }  
  46.   
  47. public class Program  
  48. {  
  49.     public static void Main()  
  50.     {  
  51.         // we can use a DI container,   
  52.         // instead of 'newing' the components here  
  53.         var dataProvider = new DataProvider(new DataValidator(new Logger()));  
  54.         bool success = dataProvider.ValidateData();  
  55.         if (success)  
  56.         {  
  57.             dataProvider.ProcessData();  
  58.             Console.WriteLine("success");  
  59.         }  
  60.         else  
  61.             Console.WriteLine("error");  
  62.     }  
  63. }  
Summary
 
To sum up, the DataValidator no longer has a dependency on DataProvider. It simply raises an event when it requires data, and anyone who has subscribed to this event will respond. This is just one way of breaking the circular dependency between components. Of course, there might be a better solution, which I would love to learn about. So, do share!


Similar Articles