Dependency Inversion
In object-oriented programming we create a lot of classes. In order for a class to be somewhat useful, it usually needs to communicate with other classes; classes depend on each other to create functionality. This dependency can also be seen as coupling between classes and different parts of our system. High coupling can make a system harder to change, and changes made can ripple through to places you didn’t foresee. Therefore we strive to have as low coupling as possible.
The Dependency Inversion Principle is one form of decoupling as it states:
- 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.
So what does this actually mean? Let’s look at a method in a BlogService
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BlogService
{
private Repository _repository;
public BlogService()
{
_repository = new Repository();
}
public void ProcessComment(Comment comment)
{
_repository.Save(comment);
// Send notification to author and do other stuff here
}
}
The BlogService
, being the high-level module, has a dependency on the Repository
, the low-level module. (A dependency, by the way, can easily be spotted by looking for the new keyword.)
To break this dependency, we are supposed to invert the dependency and make both the Repository
and BlogService
dependent to an abstraction.
To create the abstraction we introduce an interface called IRepository
. In order to make the BlogService
depend upon the abstraction, we need to remove the creation of the Repository
inside of the BlogService
. Instead we inject an IRepository
in the constructor when we create a BlogService
instance. This way the BlogService
is decoupled from the Repository, and it does not really care what repository it gets, as long as it implements the IRepository
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IRepository
{
void Save(Comment comment);
}
public class BlogService
{
private IRepository _repository;
public BlogService(IRepository repository)
{
_repository = repository;
}
public void ProcessComment(Comment comment)
{
_repository().Save(comment);
// Send notification to author and do other stuff here
}
}
To make the Repository
depend on an abstraction and make it useful for the BlogService
, it simply needs to implement the IRepository
interface.
1
2
3
4
5
6
7
public class Repository : IRepository
{
public void Save(Comment comment)
{
// Save the comment
}
}
The dependency between BlogService
and Repository
is now removed and both depends upon an abstraction; the IRepository
. Both classes can easily be changed, and even replaced without affecting each other.
Who “owns” the abstraction?
Let’s say the BlogService
belongs in the Domain or Business Layer and the Repository
belongs in the Persistence or Data Access Layer. Before we introduced DI this made the Domain Layer depend upon the Persistence Layer. To invert this dependency we should make the Persistence Layer dependent on the Domain Layer. Therefore the IRepository
belongs in the Domain Layer.