tldr;
How is DI applied if A needs multiple instances of B? Do I simply pass a list of B objects to A's constructor or setter method?
If you need multiple instances of the same object, then a common solution is to inject a provider or factory object that can later be used to access the specific instances that you need on demand. If you are manually injecting then you could use Named instances resolved from the names of the arguments or properties, but in most cases this will tightly couple the concepts, you're now closer to a Provider implementation and not really DI at all.
How is DI applied if B
cannot be created ahead of time, but only when A
actually has a need for it?
DI is a factory implementation that is invoked dynamically when object A
is instantiated to resolve the instances of any dependent objects required either for the constructor or for the properties on the instance of object A
that is being created.
At the time when A
is created, DI looks for an appropriate instance of B
, if no instance is found, it is created. It is not necessary and in many cases not very efficient to create all objects ahead of time and you should only do this if B
is a singleton and your application absolutely needs to have the instance of B
available from the start
How is DI applied if B needs some information from A in order to be created?
That is a key tenet of the DI pattern, to decouple the creation of B
from A
. If A
is dependent on B
, then B
really cannot be dependent on A
. Especially in DI Containers, dependency resolution occurs at the initialization stage, B
needs to be instantiated first, to allow A
to be created, at the time that B
is created, A
does not yet exist.
You cannot use DI to inject B
into A
in this scenario, you will need to go back to manual injection methods or redesign your classes to decouple them. The Dependency Injection Pattern helps you to identify and decouple these dependencies.
- You can still use your DI frameworks to manually implement a co-dependency scenario but doing so is not really DI anymore.
Lets Explore DI:
DI is not specifically about creating object instances ahead of time, DI is an Inversion of Control (IoC) pattern built upon the Factory pattern that abstracts the creation of dependencies away from the implementation. A common implementation of DI is to use Dependency Containers to create object instances and automatically resolve their dependent arguments of properties through type registrations/configurations and conventions.
- It is in this registration that the arguments for the creation of instances are resolved.
- In many frameworks the configuration can be via XML, or other settings providers, it doesn't necessarily have to be code or at least your code that performs this registration.
So DI is about decoupling the logic that creates the dependent objects, not specifically ahead of time, it just manages the creation of and access to objects for the duration of specific scopes.
- In Factory pattern you would explicitly call methods on the factory to provide or prepare object instances in a certain way, in DI we do not constrain or try to influence how the object was created, just the type and if needed, the name of the expected instance.
While many simple examples do cover Singleton implementations, most DI frameworks will allow you define a scope that will constrain the lifetime of the objects that are created.
Spring: When the Spring Framework creates a bean, it associates a scope with the bean. A scope defines the runtime context within which the bean instance is available. In Spring, a bean can be associated with the following scopes:
- Singleton
- Prototype
- Request
- Session
- Global session
- Application
Note: Out of the preceding scopes, Request, Session, and Application are for beans in Web-aware applications. Global session beans are for portlets.
You can configure your type to be instantiated for every new class instance, so not retain a common instance at all (prototype
scope in Spring), it is also common in state-less web application design to use Request based scopes, where all objects created to service the same HTTP Request will share the same objects.
You can also manually request instances from the DI provider, this allows you to obtain instances on demand for custom or complex scenarios or to request specific named instance implementations if your framework supports them.
- Using named instances allows multiple instances of the same type to be resolved from the DI provider, but due to the name variable it can't be used for default instance injection via Dependency Containers, you must manually request the instances.