Dependency Inversion Principle In C#

We will be discussing the Dependency Inversion Principle also known as DIP, as one of the SOLID principles of object-oriented programming and how to implement it when designing our software.

This principle said that high-level modules should not depend on low-level modules. Both should depend upon abstractions. In our example, DmartBusinessLogic depends on the DmartDataAccess class, so DmartBusinessLogic is a high-level module and DmartDataAccess is a low-level module. So, as per the first rule of DIP, DmartBusinessLogic should not depend on the concrete DmartDataAccess class, instead, both classes should depend on abstraction.

Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

Example

Let us define a class to violates DIP that contains code.

Create a class name of Dmart.cs.

public class Dmart {
    public int ID {
        get;
        set;
    }
    public string ProductName {
        get;
        set;
    }
    public string ProductQty {
        get;
        set;
    }
    public int Stock {
        get;
        set;
    }
}

Create a Data access class to fetch the records from any outsource database.

public class DmartDataAccess {
    public Dmart GetDmartDetail(int id) {
        Dmart dmart = new() {
            ID = id,
                ProductName = "Sugar",
                ProductQty = "20Kg",
                Stock = 100
        };
        return dmart;
    }
}

Create a data access factory class to perform the logic into this class to return the data list.

public class DmartDataAccessFactory {
    public static DmartDataAccess GetDmartDataAccess() {
        return new DmartDataAccess();
    }
}

Create business logic class to perform CURD operation what project needs to require.

public class DmartBusinessLogic {
    DmartDataAccess _DmartDataAccess;
    public DmartBusinessLogic() {
        _DmartDataAccess = DmartDataAccessFactory.GetDmartDataAccess();
    }
    public Dmart GetEmployeeDetails(int id) {
        return _DmartDataAccess.GetDmartDetail(id);
    }
}

The DmartBusinessLogic class depends on DmartDataAccess class, so here the DmartBusinessLogic class is the high-level module and the DmartDataAccess class is the low-level module.

So, as per the first rule of DIP, the DmartBusinessLogic class should not depend on the concrete DmartDataAccess class, instead, both the classes should depend on abstraction.

The second rule of DIP state that Abstractions should not depend on details. Details should depend on abstractions.

Let modify the code respective DIP principle.

Create interface for IDmartDataAccess, and use for everywhere for like dmartBusinessLogic, etc.

public interface IDmartDataAccess {
    Dmart GetDmartDetail(int id);
}
public class DmartDataAccess: IDmartDataAccess {
    public Dmart GetDmartDetail(int id) {
        Dmart dmart = new() {
            ID = id,
                ProductName = "Sugar",
                ProductQty = "20Kg",
                Stock = 100
        };
        return dmart;
    }
}
public class DmartDataAccessFactory {
    public static IDmartDataAccess GetDmartDataAccess() {
        return new DmartDataAccess();
    }
}
public class DmartBusinessLogic {
    readonly IDmartDataAccess _DmartDataAccess;
    public DmartBusinessLogic() {
        _DmartDataAccess = DmartDataAccessFactory.GetDmartDataAccess();
    }
    public Dmart GetEmployeeDetails(int id) {
        return _DmartDataAccess.GetDmartDetail(id);
    }
}

Conclusion

Even though the name of the principle is self-explanatory, we can see how easy it is to implement. Make sure to distinguish the responsibility of every class.