Depend on abstractions, not on concretions.
Robert C. Martin
Welcome back to the series on SOLID. By now, I’ll assume you’ve read my earlier post on the Interface Segregation Principle. In this post, we’ll be investigating the Dependency Inversion Principle which is not to be confused with Dependency Injection. Dependency Injection is only a form of the Inversion Principle. The actual principle is about depending on abstractions and decoupling your classes. As we know, closely coupled code means that you can’t test one aspect of the code without the other and you can’t swap the implementation at runtime etc.
Breaking the principle
Firstly, let’s look at a bad example. Let’s say you have a class, a repository class. Inside this repository class, you need a database. So, you decide that you’ll new one up, right?
public class Database { public T RunQuery<T>(string query, Object parameters) where T : new() { return new T(); } } public class Hotel { public Guid Id { get; set; } public string Name { get; set; } } public class Repository { private readonly Database database; public Repository() { database = new Database(); } public Hotel GetHotel(Guid guid) { return database.RunQuery<Hotel>("SELECT * FROM Hotel WHERE HotelId = {hotelId}", new {hotelId = guid}); } }
But now – at runtime, you have two databases you need to connect to. One is newly created, and all this new data from another app is located there. You need to access both the database for your app and the database for the other app. How can you do that when your Repository is so closely coupled with your Database? You’ll have to create a new Repository that connects to this other database, right?
Fix using the principle
No. Here’s where dependency injection can be useful (a form of Dependency Inversion Principle). What we’ll do is inject the Database into the Repository. That means, at runtime, we’ll no longer depend on a single Database. We can go even further and depend on an abstraction of the database. That way – we can use whatever SQL DB or NoSQL DB we want.
public interface IDatabase { T RunQuery<T>(string query, Object parameters) where T : new(); } public class Database : IDatabase { public T RunQuery<T>(string query, Object parameters) where T : new() { return new T(); } } public class Hotel { public Guid Id { get; set; } public string Name { get; set; } } public class Repository { private readonly IDatabase database; public Repository(IDatabase database) { this.database = database; } public Hotel GetHotel(Guid guid) { return database.RunQuery<Hotel>("SELECT * FROM Hotel WHERE HotelId = {hotelId}", new {hotelId = guid}); } }
If you wanted to take it a step further, you would probably remove the SQL code and put that somewhere else where changing that won’t change the Repository.
D for Dependency Inversion Principle
D for do make sure you follow this rule. You want your code to be as decoupled as possible. This will allow you to write flexible code that should stand the test of time. Dependency Injection is probably the most well-known way to implement Dependency Inversion Principle, and it’s the easiest.
Final words: I really hoped you understood my series on SOLID. Please comment on if this has helped you at all or how I can improve my technical blog posts. I do enjoy critical feedback so if I’ve explained something wrong – do tell me.
Pitrak says
Thanks a lot for the explanation. It’s one of the best explanations I could find out there on the web. The duck example for liskov’s is the best.