⬆️ ⬇️

Introducing dependencies through fields is bad practice

Translation of the article Field Dependency Injection Considered Harmful by Vojtech Ruzicka



image



Introducing dependencies across fields is a very popular practice in DI frameworks such as Spring. However, this method has some serious compromises and therefore it is worthwhile to avoid it more often.



Types of implementations



There are three main ways to inject your dependencies into a class: through a constructor, a setter, and a field. Let's briefly compare the code with the same dependencies implemented using each approach.

')

Constructor



private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) { this.dependencyA = dependencyA; this.dependencyB = dependencyB; this.dependencyC = dependencyC; } 


Setter



 private DependencyA dependencyA; private DependencyB dependencyB; private DependencyC dependencyC; @Autowired public void setDependencyA(DependencyA dependencyA) { this.dependencyA = dependencyA; } @Autowired public void setDependencyB(DependencyB dependencyB) { this.dependencyB = dependencyB; } @Autowired public void setDependencyC(DependencyC dependencyC) { this.dependencyC = dependencyC; } 


Field



 @Autowired private DependencyA dependencyA; @Autowired private DependencyB dependencyB; @Autowired private DependencyC dependencyC; 


What's wrong?



As you can see, the implementation option through the field looks very attractive. It is very laconic, expressive, there is no sample code. The code is easy to navigate and read. Your class can simply focus on the core functionality and is not cluttered with the template DI code. You simply place the @Autowired annotation over the field - that's all. It is not necessary to write special constructors or setters only for the DI container to provide the necessary dependencies. Java is quite verbose in and of itself, so it’s worth using every opportunity to make the code shorter, right?



Violation of the principle of sole responsibility



Adding new dependencies is easy. Perhaps even too simple. There is no problem to add six, ten or even more dependencies. When using constructors for implementation, after a certain moment the number of arguments of the constructor becomes too large and it immediately becomes obvious that something is wrong. Having too many dependencies usually means that the class has too many areas of responsibility. This can be a violation of the principles of single responsibility (single responsibility) and division of responsibility ( original: separation of concerns ) and is a good indicator that the class may need to be more carefully studied and refactored. When using intrusion through the fields, there is no such clear alarm indicator, and thus the unlimited growth of the embedded dependencies occurs.



Hiding dependencies



Using a DI container means that the class is no longer responsible for managing its dependencies. The responsibility for obtaining them is taken out of the class to the outside and now someone else is responsible for providing them: it can be a DI-container or manually giving them through tests. When a class is no longer responsible for obtaining dependencies, it must explicitly interact with them using public interfaces — methods or constructors. Thus, it becomes clear what the class requires, as well as whether it is optional dependencies (via setters) or mandatory (constructor)



Dependence on the DI-container



One of the key ideas of DI frameworks is that the managed class should not depend on the particular container used. In other words, it should be a simple POJO class, an instance of which can be created independently if you pass it all the necessary dependencies. Thus, you can create it in a unit test without launching the container and test it separately (with the container it will be more like an integration test). If there is no strings attached to the container, you can use the class as managed or unmanaged, or even switch to another DI framework.



However, when implemented directly in the fields, you do not provide a direct way to create an instance of a class with all the necessary dependencies. It means that:





Immutability



Unlike the constructor method, embedding through fields cannot be used to assign dependencies to final fields, which causes your objects to become mutable



Implementation through the designer vs setter



Thus, the injection through the fields may not be a good way. What remains? Setters and constructors. Which one should I use?



Setters



Setters should be used for injecting optional dependencies. The class must be able to function, even if they have not been provided. Dependencies can be changed at any time after an object is created. This may or may not be an advantage, depending on the circumstances. Sometimes it is preferable to have an immutable object. Sometimes it is useful to change the constituent parts of an object at run time — for example, managed MBean beans in JMX.

The official recommendation from the Spring 3.x documentation encourages the use of setters on constructors:

The Spring command mainly advocates injection through setters, because a large number of constructor arguments can become cumbersome, especially if the properties are optional. Setters also make objects of this class suitable for reconfiguration or re-injection later. Management via JMX MBeans is a prime example.



Some purists prefer designer-based injection. Providing all dependencies means that the object always returns to the calling code in a fully initialized state. The disadvantage is that the object becomes less susceptible to reconfiguration and re-injection


Constructors



Injection through constructors is good for mandatory dependencies - those that are required for the correct functionality of the object. Passing them through the constructor, you can be sure that the object is fully ready for use from the moment it is created. The fields assigned in the constructor can also be final, which allows the object to be completely unchanged or at least protects the required fields.



One of the consequences of using embedding through a constructor is that it is no longer possible to have a cyclic relationship between two objects created in this way (as opposed to embedding through a setter). This is a plus rather than a limitation, since cyclic dependencies should be avoided, which is usually a sign of a poor architecture. This prevents this practice.



Another advantage is that when using Spring versions 4.3+ you can completely untie your class from a specific DI framework. The reason is that Spring now supports implicit injection through a constructor for single-use usage scenarios. This means that you no longer need DI annotations in your class. Of course, you can achieve the same result by explicitly configuring the DI in the Spring settings for this class; just now it's much easier.



As for Spring 4.x, the official recommendation from the documentation has changed and now injection through the setter is no longer preferable over the constructor:

The Spring command primarily advocates injection through the constructor, because it allows the application components to be implemented as immutable objects and to ensure that the required dependencies are not null . Moreover, components embedded through a constructor are always returned to the client code in a fully initialized state. As a small note, a large number of constructor arguments is a sign of “code with a snuff” and implies that the class probably has too many responsibilities and needs to be reorganized in order to better resolve the issue of sharing responsibility.



Setter injection should be used primarily for optional dependencies, which can be assigned default values ​​within a class. Otherwise, checks for not-null should be used wherever the code uses these dependencies. One of the advantages of using deployment via setters is that they make class objects re-configurable and re-injected later.


Conclusion



Basically you should avoid embedding through the fields. As an alternative for implementation, use setters or constructors. Each of them has its advantages and disadvantages depending on the situation. However, since these approaches can be mixed, this is not the choice of either “or-or” and you can combine the injection through a setter and a constructor in the same class. Constructors are more suitable for mandatory dependencies and when needed in immutable objects. Setters are better suited for optional dependencies.

Source: https://habr.com/ru/post/334636/



All Articles