Introduction
Trying to use Scalable Frontend 1 — Architecture Fundamentals When it comes to Dependency Injection, I feel a little unclear. I have found some information to understand it better. It is summarized in this translation record.
What is dependency injection
In software engineering, dependency injection is a technology that an object receives other objects it depends on. These other objects are called dependencies. In a typical "usage" relationship, the receiving object is called a client, and the delivered (i.e. "injected") object is called a service. The code that passes the service to the client can be many types of things called injectors. The injector tells the client what service to use, rather than specifying which service to use. "Injection" refers to passing a dependency (service) to the object (client) that will use it.
The service is part of the client state. Delivering services to clients, rather than letting clients build or find services, is the basic requirement of this pattern.
advantage
- Dependency injection allows the client to configure flexibly. Only the behavior of the client is fixed. The client can operate on anything that supports the internal interface expected by the client.
- Dependency injection can be used to externalize the configuration details of the system into the configuration file, allowing the system to be reconfigured without recompiling. You can write separate configurations for different situations where components need different implementations. This includes but is not limited to testing.
- Because dependency injection does not require any changes to the code behavior, it can be applied to the refactoring of legacy code. The result is that the client is more independent and easier to unit test with stubs or mock objects. This testability is usually the first benefit you notice when using dependency injection.
- Dependency injection allows the client to remove all the knowledge needed for a specific implementation. This helps isolate the client from the impact of design changes and defects. It enhances reusability, testability and maintainability.
- Reduce boilerplate code in application objects because all the work of initializing or setting dependencies is handled by the provider component.
- Dependency injection allows parallel or independent development. Two developers can independently develop classes that use each other, and only need to know what interface the classes will communicate through. Plug ins are usually developed by third-party stores, and they never even communicate with developers who develop products that use plug-ins.
- Dependency injection reduces the coupling between classes and their dependencies.
inferiority
- The client created by dependency injection needs to be provided with configuration details by the construction code. This can be onerous when obvious defaults are available.
- Dependency injection makes code difficult to track (read) because it separates behavior from construction. This means that developers must reference more files to track the implementation of the system.
- The dependency injection framework is implemented through reflection or dynamic programming. This hinders the use of IDE automation, such as find references, show call levels, and security refactoring.
- Dependency injection usually requires more pre development work, because it is impossible to summon something to be the right thing at the time and place, but it must be required to be injected and ensure that it has been injected.
- Dependency injection forces complexity from classes to connections between classes, which may not always be ideal or easy to manage.
- Dependency injection may encourage dependency based on the dependency injection framework.
Several forms of dependency injection
There are at least three ways for client objects to receive references to external modules: constructor injection, setter injection and interface injection.
Constructor Injection
Dependencies are passed in through parameters provided by the client's class constructor.
// Constructor Client(Service service) { // Save the reference to the passed-in service inside this client this.service = service; }
It is best used when all dependencies can be constructed first, because it can be used to ensure that the client object is always in a valid state, rather than making some of its dependency references null (not set). But in itself, it lacks the flexibility to change its dependencies later. This may be the first step in making the client immutable to achieve thread safety.
// Constructor Client(Service service, Service otherService) { if (service == null) { throw new InvalidParameterException("service must not be null"); } if (otherService == null) { throw new InvalidParameterException("otherService must not be null"); } // Save the service references inside this client this.service = service; this.otherService = otherService; }
Setter injection
This approach requires the client to provide a setter to the dependency.
// Setter method public void setService(Service service) { // Save the reference to the passed-in service inside this client. this.service = service; }
The client is required to provide a setter for each dependency. This allows you to freely manipulate the state of the dependency reference at any time. This provides flexibility, but if multiple dependencies are to be injected, it is difficult for the client to ensure that all dependencies have been injected before they can be used.
Because these injections occur independently, it is impossible to determine when the injector has completed the connection with the client. A dependency may remain null simply because the call to its setter failed. This will force you to check whether the injection from the time of client assembly to the time of use has been completed.
// Set the service to be used by this client public void setService(Service service) { this.service = service; } // Set the other service to be used by this client public void setOtherService(Service otherService) { this.otherService = otherService; } // Check the service references of this client private void validateState() { if (service == null) { throw new IllegalStateException("service must not be null"); } if (otherService == null) { throw new IllegalStateException("otherService must not be null"); } } // Method that uses the service references public void doSomething() { validateState(); service.doYourThing(); otherService.doYourThing(); }
Interface injection
This is just the setting method for the client to publish the role interface to the client dependency. It can be used to determine how the injector should talk to the client when injecting dependencies.
// Service setter interface. public interface ServiceSetter { public void setService(Service service); } // Client class public class Client implements ServiceSetter { // Internal reference to the service used by this client. private Service service; // Set the service that this client is to use. @Override public void setService(Service service) { this.service = service; } }
The advantage of interface injection is that dependencies can completely ignore their clients, but they can still receive references to new clients and use it to send their own references back to clients. In this way, the dependency becomes an injector. The key is that the injection method (perhaps just a classic setter method) is provided through an interface.