S.O.L.I.D Design Principles Explained: Part 5

In the Fourth part of this series we discussed the following:

  • Interface Segregation Principle.
  • Also discussed how to make thin interfaces for better modularity and granularity of code.

In this article, we discuss the last acronym "D" in the "SOLID" i.e. the Dependency Inversion Principle.

Dependency Inversion Principle (DIP) states that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

This principle basically tries to address one of the decoupling forms of coding where a higher level should never depend upon the lower-level modules and we are going to see how it is achievable in this article. Open the code base we have used in our previous article.
 
Note: I want to make a point that I am using the ReSharper tool in my Visual Studio and so the attachments have the related files. If you do not have ReSharper installed in your desktops and this code gives you error, you can download the ReSharper tool for a trial version from the ReSharper site.

Ok. Now returning to the DIP. First go to the Program class in the Client project and you see that the Program class is directly dependent on the EmployeeData class. Now, any changes to the EmployeeData class will affect the Program class directly.

EmployeeData employeeData = new EmployeeOracleData();
"employeeData.DisplayEmployeeData();
Console.ReadLine();

So, what we are going to do is to refactor the code for the class EmployeeData by introducing an interface as in the following:

public interface IEmployeeData

{

    void DisplayEmployeeData();

}

 

public abstract class EmployeeData : IEmployeeData

{

    public abstract void DisplayEmployeeData();

}


Now we will change our Program class with the new interface implementation.

IEmployeeData employeeData = new EmployeeOracleData();

Also if we check the EmployeeOracleData, there is a concrete relationship between EmployeeOracleData and DataFormatter class. So, we need to introduce an interface in the DataFormatter class.

Our code before interface introduction:

public class DisplayEmployeeOracleData : DisplayEmployee

    {

        public override  void DisplayEmployeeData()

        {

            DataFormatter dataFormatter = new OracleDataFormatter();

            string empData = dataFormatter.FormatEmployeeData();

Console.WriteLine("Displaying Employee Data for Oracle Database as below:");

            Console.WriteLine(empData);

        }

    }

Now change DataFormatterClass as in:

public
interface IDataFormatter

{

    string FormatEmployeeData();

}

 

public abstract class DataFormatter : IDataFormatter

{

    public abstract string FormatEmployeeData();

}

 Now we change our code as:

IDataFormatter dataFormatter = new OracleDataFormatter();

Similarly, throughout our project wherever we see two classes are concretely bound we are going to introduce an interface and let our classes be related through interfaces not through concrete classes.

Now my code looks something like this:

class Program

{

    static void Main(string[] args)

    {

        IEmployeeData employeeData = new EmployeeOracleData();

        …

    }

}

public interface IDataFormatter

{

    string FormatEmployeeData();

}

 

public abstract class DataFormatter : IDataFormatter

{

    public abstract string FormatEmployeeData();

}

public interface IDisplayEmployee

{

    void DisplayEmployeeData();

}

 

public abstract class DisplayEmployee : IDisplayEmployee

{

    public abstract void DisplayEmployeeData();

}

public class DisplayEmployeeOracleData : DisplayEmployee

{

    public override  void DisplayEmployeeData()

    {

        IDataFormatter dataFormatter = new OracleDataFormatter();

    }

}

public class DisplayEmployeeSqlData : DisplayEmployee

{

    public override void DisplayEmployeeData()

    {

        IDataFormatter dataFormatter=new SqlDataFormatter();

       

    }

}

public interface IEmployeeData

{

    void DisplayEmployeeData();

}

 

public abstract class EmployeeData : IEmployeeData

{

    public abstract void DisplayEmployeeData();

}

public class EmployeeOracleData : EmployeeData

{

    public override void DisplayEmployeeData()

    {

IDisplayEmployee displayEmployee = new DisplayEmployeeOracleData();

        displayEmployee.DisplayEmployeeData();

    }

}

public class EmployeeSqlData : EmployeeData

{

 

  public override void DisplayEmployeeData()

  {

      IDisplayEmployee displayEmployee = new DisplayEmployeeSqlData();

      displayEmployee.DisplayEmployeeData();

  }

 

 

}

public class OracleDataFormatter : DataFormatter

{

    public override string FormatEmployeeData()

    {

        IEmployeeDataAccess dataAccess = new OracleDataAccess();

        Console.WriteLine("Getting Employee Data...");

        var employessList = dataAccess.GetEmployeeData();

 

    }

}

public class SqlDataFormatter : DataFormatter

{

    public override string FormatEmployeeData()

    {

        IEmployeeDataAccess dataAccess=new SqlDataAccess();

       

    }

}

 So, we have taken care of replacing concrete class relationships with the Interfaces throughout the solution.

Just to make sure it did not break anywhere, let us run to check the output.

SOLIDDSN1.jpg

Good to see nothing broke with our changes.

Now returning to the Program class we find that it is still dependent on the EmployeeOracleData class.

IEmployeeData employeeData = new EmployeeOracleData();

So now we should eliminate this dependency too by refactoring the following code:
 

public class EmployeeOracleData : EmployeeData

  {

      public override void DisplayEmployeeData()

      {

IDisplayEmployee displayEmployee = new DisplayEmployeeOracleData();

          displayEmployee.DisplayEmployeeData();

      }

  }

So, first what we are going to do is introduce the displayEmployee as a property in the class and we introduce a constructor to set the value of private property _displayEmployee.

public
class EmployeeOracleData : EmployeeData

    {

        private readonly IDisplayEmployee _displayEmployee;

        public override void DisplayEmployeeData()

        {

          

            _displayEmployee.DisplayEmployeeData();

        }

        public EmployeeOracleData(IDisplayEmployee displayEmployee)

        {

            _displayEmployee = displayEmployee;

        }

    }

So, now we need to modify our Program class appropriately with the new change.

IEmployeeData employeeData = new EmployeeOracleData(new DisplayEmployeeOracleData());

Now the program class is entirely independent of any concrete class relationship. It has full control of what type of IDisplayEmployee instance it wants to pass. Now compile the program to see if it is working fine.

SOLIDDSN2.jpg

Now let us do the same thing in the entire solution by introducing constructors in each class to pass the instances of classes by eliminating the dependencies between the classes.
 

public class DisplayEmployeeOracleData : DisplayEmployee

    {

        private readonly IDataFormatter _dataFormatter;

 

        public override  void DisplayEmployeeData()

        {

            string empData = _dataFormatter.FormatEmployeeData();

Console.WriteLine("Displaying Employee Data for Oracle Database as below:");

            Console.WriteLine(empData);

        }

 

        public DisplayEmployeeOracleData(IDataFormatter dataFormatter)

        {

            _dataFormatter = dataFormatter;

        }

 

    }

Since we have changed this class, now we need to provide the dataFormatter from our Program class.

IEmployeeData
employeeData = new EmployeeOracleData(new DisplayEmployeeOracleData(new OracleDataFormatter()));

See now our higher level code i.e. code in the Program class is dictating the lower level code i.e., DisplayEmployeeOracleData and OracleDataFormatter instances.

There are many inversion control tools available which decides at run time what instance of the class should be provided to the constructor or the methods.

Note: We do not need to control everything from the Client project. For example, the client should not be handling the database access. It should be handled only through the Business Logic Layer and the client should not be coupled with the Data access stuff. So we need to think about the practically of what level we need the control to be so it is reasonable. Since we are not using inversion controls or any tools I am restricting the Dependency Inversion Principles between the Client and Business Logic layers. In real time, there may be a configuration tool that dictates what Data Access class must be instantiated and it provides the class reference to be instantiated in the Client (View Layer) which the client passes through the Business Layer to perform the actual task. This part I have eliminated in my code example. If I get to write another article on this, I will try to accommodate that too.

This ends our last part of the series and the code base is attached to the article.

Please note that all these series of articles are based on my understanding of the design principles and any opinions expressed in this article are purely my own and if they are contradictory then you are free to follow any other opinions or articles from other sources.

Hope you like this article too.

Up Next
    Ebook Download
    View all
    Learn
    View all