Dependency Inversion Principle (DIP) in C#
In my previous articles I wrote about Solid Principles in C#.
In this article, I am going to show you when and how to use the Dependency Inversion Principle (DIP) in C# with an example project. You can find the repository on GitHub.
The master branch shows the initial code used in the example. There are separate tags and branches for each of the all solid principles that you can review or download as well. Here are links you can use to jump to these tagged versions in your browser:
What is the Dependency Inversion Principle (DIP) in C#?
The Dependency Inversion Principle (DIP) is the last, in alphabetical order, of the SOLID design principles. We can always reuse the definition from Wikipedia.
High-level modules should not import anything from low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
Let’s try to summarize these two definitions:
- Instead of high-level modules relying directly on low-level modules, both should communicate through abstract interfaces. This promotes flexibility and allows for easier substitution of components.
- Abstractions should not be tightly coupled to specific implementations; instead, the concrete details should adhere to the abstractions. This allows you to change or extend implementations without affecting the overall structure of your code.
Abstractions describe what:
- Send a message;
- Store a record;
Details specify how to do:
- Send a push notification to a device;
- Deserialize an object and store in a database;
In this example we expose implementation details and making the interface much less reusable. The interface should not know the details of the database, in this case mysql.
public interface IOrderDataAccess { MySqlDataReader GetOrders(MySqlParameterCollection params); }
This example hide the details above and make the interface reusable.
public interface IOrderDataAccess { List<Order> GetOrders(Dictionary<string, string> params); }
What are Abstractions in C#?
In C#, there are two types of abstractions:
- C# Interface, Interfaces define a contract that classes must adhere to by implementing their methods, properties, events, or indexers. You can have as many interfaces as needed in your code, and a single class can implement multiple interfaces.
- C# Abstract Class, Abstract classes provide a way to create a partial implementation of a class while leaving some methods or properties to be implemented by derived classes. You can have multiple abstract classes in your code, and a single class can inherit from one abstract class while implementing multiple interfaces.
Dependency Inversion Principle (DIP) and Dependency Injection (DI) are they the same thing?
No, the Dependency Inversion Principle (DIP) is a higher-level design principle that emphasizes the use of abstractions and the reduction of direct dependencies between modules, while Dependency Injection (DI) is a practical technique used to implement DIP.
Dependency Injection (DI) allows you to achieve Dependency Inversion Principle (DIP) by injecting dependencies into classes or components from the outside.
Why should you use the Dependency Inversion Principle (DIP)?
- Flexibility and Extensibility. It promotes loose coupling between modules or components by depending on abstractions rather than concrete implementations.
- Testability. It makes it easier to write unit tests for your code. By depending on abstractions, you can easily create mock or stub implementations of dependencies for testing purposes.
- Parallel Development. It enables parallel development by allowing teams or individuals to work on different parts of the system independently.
- Improved Code Reusability. Abstractions created in accordance with Dependency Inversion Principle (DIP) can be reused across different parts of your application or even in entirely different projects.
- Separation of Concerns. It encourages better separation of concerns in your code. High-level modules focus on defining what needs to be done (abstractions), while low-level modules handle how things are done (concrete implementations).
How can i use the Dependency Inversion Principle (DIP)?
These are the typical approaches to Dependency Inversion Principle (DIP):
- Use Abstractions (Interfaces or Abstract Classes).
// Define an abstraction (interface)
public interface IDataProvider {
string GetData();
}
- Implement Concrete Classes That Adhere to Abstractions.
// Implement a concrete class that adheres to the abstraction public class SqlDataProvider : IDataProvider { public string GetData() { // Implement data retrieval from SQL return "Data from SQL"; } }
- Apply Dependency Injection (DI).
public class DataProcessor { private readonly IDataProvider dataProvider; // Use constructor injection to inject the dependency public DataProcessor(IDataProvider dataProvider) { this.dataProvider = dataProvider; } public string ProcessData() { // Use the injected dependency var data = dataProvider.GetData(); // Process the data return "Processed: " + data; } }
- Configure and Use an IoC Container (Optional).
services.AddScoped<IDataProvider, SqlDataProvider>(); services.AddTransient<DataProcessor>();
How we can refactor the code applying the Dependency Inversion Principle (DIP)?
Please have a look at how I have refactored my previous code here DIP-END
If you think your friends/network would find this useful, please share it with them. I’d really appreciate it.
Thanks for reading!