Dependency injection
- Transfer
From translator
The translation you are introducing opens a series of articles from Jakob Jenkov about dependency injection, or DI. The series is noteworthy in that the author, analyzing the concepts and practical application of such concepts as “dependency”, “dependency injection”, “container for dependency injection”, comparing patterns of creating objects, analyzing the shortcomings of specific implementations of DI containers (for example, Spring) , tells how he came to writing his own DI container. Thus, the reader is invited to get a fairly holistic view of the issue of dependency management in applications.
This article compares the approach to configuring objects from the inside and outside (DI). In the sense, this article continues the article by Jakob Jenkov Understanding Dependencies, which defines the very concept of "dependence" and their types.
The series includes the following articles
- Dependency injection
- Dependency Injection Containers
- Dependency Injection Benefits
- When to use Dependency Injection
- Is Dependency Injection Replacing the Factory Patterns?
Dependency Injection
Dependency Injection is an expression first used in Martin Fowler's article Inversion of Control Containers and the Dependency Injection Pattern . This is a good article, but it overlooks some of the benefits of dependency injection containers. I also do not agree with the conclusions of the article, but more on that in the following texts.
Dependency Injection Explanation
Video from the author with illustrative examples in the text of the articleDependency injection is an object customization style in which the fields of an object are set by an external entity. In other words, objects are customizable by external objects. DI is an alternative to self-tuning objects. This may look somewhat abstract, so let's look at an example:
UPD: after discussing the code fragments presented by the author with flatscode and fogone , I decided to correct the controversial issues in the code. The original idea was not to touch the code and give it as it was written by the author. The original copyright code in disputed places is commented out with the indication “in the original”, its corrected version is given below. Also, the original code can be found at the link at the beginning of the article.
public class MyDao {
//в оригинале: protected DataSource dataSource =
private DataSource dataSource =
new DataSourceImpl("driver", "url", "user", "password");
//data access methods...
public Person readPerson(int primaryKey) {...}
}
This DAO (Data Access Object), MyDao needs an instance of javax.sql.DataSource in order to get database connections. Database connections are used to read and write to the database, for example, Person objects.
Note that the MyDao class creates an instance of DataSourceImpl, as it needs a data source. The fact that MyDao needs a DataSource implementation means that it depends on it. It cannot do its job without implementing a DataSource. Therefore, MyDao has a “dependency” on the DataSource interface and on some implementation of it.
The MyDao class instantiates a DataSourceImpl as an implementation of a DataSource. Therefore, the MyDao class itself "resolves its dependencies." When a class resolves its own dependencies, it automatically also depends on the classes for which it resolves dependencies. In this case, MyDao also depends on DataSourceImpl and four hard-coded string values that are passed to the DataSourceImpl constructor. You can neither use other values for these four lines, nor use another implementation of the DataSource interface without changing the code.
As you can see, when a class resolves its own dependencies, it becomes inflexible with respect to these dependencies. This is bad. This means that if you need to change the dependencies, you need to change the code. In this example, this means that if you need to use a different database, you will need to change the MyDao class. If you have many DAO classes implemented this way, you will have to modify them all. In addition, you cannot conduct MyDao unit testing by locking the DataSource implementation. You can use only DataSourceImpl. It does not take a lot of mind to understand that this is a bad idea.
Let's change the design a bit:
public class MyDao {
//в оригинале: protected DataSource dataSource = null;
private final DataSource dataSource;
public MyDao(String driver, String url, String user, String password){
this.dataSource = new DataSourceImpl(driver, url, user, password);
}
//data access methods...
public Person readPerson(int primaryKey) {...}
}
Note that instantiating the DataSourceImpl has been moved to the constructor. The constructor accepts four parameters, these are the four values required for the DataSourceImpl. Although the MyDao class still depends on these four values, it no longer resolves the dependency itself. They are provided by the class that instantiates MyDao. Dependencies are “injected” into the MyDao constructor. Hence the term "implementation (approx. Transl .:: or otherwise - injection) of dependencies." Now it is possible to change the database driver, URL, username or password used by the MyDao class without changing it.
Dependency injection is not limited to designers. Dependencies can also be implemented using setter methods, or directly through public fields ( approx. Transl .: the translator disagrees about the fields, this violates the protection of class data).
The MyDao class may be more independent. Now it still depends on both the DataSource interface and the DataSourceImpl class. There is no need to depend on anything other than the DataSource interface. This can be achieved by injecting a DataSource into the constructor instead of four string type parameters. Here's what it looks like:
public class MyDao {
//в оригинале: protected DataSource dataSource = null;
private final DataSource dataSource;
public MyDao(DataSource dataSource){
this.dataSource = dataSource;
}
//data access methods...
public Person readPerson(int primaryKey) {...}
}
Now the MyDao class no longer depends on the DataSourceImpl class or on the four lines required by the DataSourceImpl constructor. Now you can use any implementation of DataSource in the constructor of MyDao.
Chain Dependency Injection
The example from the previous section is a bit simplified. You may argue that the dependency is now moved from the MyDao class to each client that uses the MyDao class. Customers now have to be aware of the DataSource implementation in order to be able to put it in the MyDao constructor. Here is an example:
public class MyBizComponent{
public void changePersonStatus(Person person, String status){
MyDao dao = new MyDao(
new DataSourceImpl("driver", "url", "user", "password"));
Person person = dao.readPerson(person.getId());
person.setStatus(status);
dao.update(person);
}
}
As you can see, now MyBizComponent depends on the DataSourceImpl class and the four lines needed by its constructor. This is even worse than the dependence of MyDao on them, because MyBizComponent now depends on classes and on information that it doesn’t even use. Moreover, the implementation of DataSourceImpl and constructor parameters belong to different layers of abstraction. The layer below MyBizComponent is the DAO layer.
The solution is to continue implementing dependency across all layers. MyBizComponent should depend only on the MyDao instance. Here's what it looks like:
public class MyBizComponent{
//в оригинале: protected MyDao dao = null;
private final MyDao dao;
public MyBizComponent(MyDao dao){
this.dao = dao;
}
public void changePersonStatus(Person person, String status){
Person person = dao.readPerson(person.getId());
person.setStatus(status);
dao.update(person);
}
}
Again, the dependency, MyDao, is provided through the constructor. Now MyBizComponent depends only on the MyDao class. If MyDao was an interface, it would be possible to change the implementation without the knowledge of MyBizComponent.
Such a dependency injection pattern should continue through all layers of the application, from the lowest layer (data access layer) to the user interface (if any).
To the beginning