DI Flashcards
On-demand approach
In the on- demand approach, whenever a consumer needs a new object-under-construction, the consumer creates or finds the dependencies needed by the object-under-constructionat the time the consumer instantiates the object-under-construction. In other words, the consumer is responsible for gathering all dependencies and is responsible for providing those dependencies to the object-under-construction via the initializer, a stored- property or a method.
What are ephemeral dependencies?
they’re created and destroyed alongside the object-under-construction.
dependencies that can be instantiated at the same time as the object-under-construction
On-demand approach, Initializing ephemeral dependencies
If dependencies don’t need to live longer than the object-under-construction, and can therefore be owned by the object-under-construction, then
the consumer can simply initialize the dependencies and provide those dependencies to the object-under- construction. These dependencies are ephemeral dependencies because they’re created and destroyed alongside the object-under-construction.
Initializing ephemeral dependencies On-demand approach
Consumer know that concrete implementation of dependencies for object-under-construction.
object-under-construction doesn’t know it, because it uses protocol types for dependencies
Pros of the on-demand approach
This approach is relatively easy to explain and relatively easy to understand.
Your code is testable because you can substitute nondeterministic side effect dependencies with deterministic fake implementations.
You can defer decisions. For example, you can use an in-memory data store implementation while you decide on a database technology. Changing from the in- memory implementation to the database implementation is easy because you can find all the in-memory instantiations and replace them with the database instantiations. This can be a bit tedious, so this is also a con that’s addressed in more advanced approaches.
• Your team can work on the same feature at the same time because one developer can build an object-under-construction while another builds the dependencies. The developer building the object-under-construction can use fake implementations of the dependencies while the other developer builds the real implementations of the dependencies.
Cons of the on-demand approach
Dependency instantiations are decentralized. The same initialization logic can be duplicated many times.
Consumers need to know how to build the entire dependency graph for an object- under-construction. Dependencies can also have dependencies and so on. The consumer might have to instantiate a lot of dependencies. This is not ideal because multiple consumers using the same object-under-construction class will have to duplicate the dependency graph instantiation logic.
Instantiating dependencies on-demand is a decentralized approach that
doesn’t scale well.
The factories approach is all about centralizing
dependency instantiation.
One goal of creating a factories class is to make it possible
for consumers to create objects-under-construction without having to know how to build dependency graphs required to instantiate objects-under-construction.
Creating and getting transitive dependencies
To create an ephemeral transitive dependency, a dependency factory method can simply call another dependency factory included in the factories class.
To get a reference to a long-lived transitive dependency, a dependency factory method should
include a parameter for the transitive dependency. By adding parameters, long-lived transitive dependencies can be provided to the dependency factory method.
Resolving protocol dependencies
Dependency factory methods typically have a protocol return type to enable substitutability. When this is true, dependency factory methods encapsulate the mapping between protocol and concrete types. This is typically called resolution because a dependency factory method is resolving which implementation to create for a particular protocol dependency. In other words, these methods know which concrete initializer to use.
Object-under-construction factory methods
Object-under- construction factory methods look just like dependency factory methods. The only difference is
object-under-construction factory methods are called from the outside of a factories class, whereas dependency factory methods are called within a factories class.
Getting runtime values factory approach
These runtime values are typically called runtime factory arguments. As the name suggests, you handle this situation by adding a parameter, for each runtime value, to the object-under-construction’s or dependency’s factory method. At runtime, the factory method caller will need to provide the required values as arguments.
Injecting factories goal
The goal is to be able to unit test an object-under-construction without needing the factories class at all.
You can give this power to objects-under-construction from the outside using one of two Swift features:
closures or protocols.
Using closures to give this power to objects-under-construction from the outside
Here are the steps:
Declare a stored-property in the object-under-construction with a signature such as:let makeUseCase: () -> UseCase.
Add an initializer parameter to the object-under-construction with the same closure type.
Go to the factories class and find the factory method that creates the object-under- construction.
Use initializer injection, in the object-under-construction factory method, to inject a closure that creates a new dependency. To do this, open a closure in the object- under-construction’s initializer call. Inside the closure, call the dependency factory method for the dependency in question and return the new instance. The closure captures the factories class instance so the object-under-construction essentially holds on to the factories object without knowing.
Now, the object-under-construction can easily create a new instance of a dependency by invoking the factory closure, whenever.
Give this power to objects-under-construction from the outside by using protocols
Steps
Factory approach
Here are the steps: Declare a new factory protocol that contains a single method for the dependency that the object-under-construction needs to create. The factories class will already conform to this protocol because the dependency factory method in the protocol should match the implemented factory method in the factories class. Simply declare conformance in the factories class. Add a stored-property and initializer parameter of the factory protocol type to the object-under-construction. This allows you to inject the factories object into the object-under-construction; however, the object-under-construction will only see the single factory method defined in the protocol. The object-under-construction does not know it is injected with the factories object because the protocol restricts the object-under-construction’s view. Go into the object-under-construction’s factory method in the factories class and update the initialization line to inject self. self is the factories object which conforms to the new factory protocol you declared.
Stateless class means
that class don’t have any stored properties and can have all methods static
Pros of the factories approach
Ephemeral dependencies are created in a central place. This gives you a lot of power to switch out entire subsystems by changing a couple of lines of code.
Substituting a large amount of dependencies during a functional UI test is much easier because all your dependencies are initialized in one class. Developers typically want to fake out the entire networking and persistence stack during UI tests because developers want deterministic tests so their builds don’t constantly break with false positives.
Consumers are more resilient to change because they no longer need to know how to build dependency graphs. That’s one less responsibility for all consumers. This helps your team work in parallel because code is more loosely coupled.
Code is generally easier to read because all of the initialization boilerplate is moved out of the classes that do interesting work.
Cons of the factories approach
In a large app, a single factories class can become extremely large. You can break up large factories classes into multiple classes.
This approach only works for ephemeral objects. Longer-lived objects need to be held somewhere. Ideally, all dependencies should be centrally managed regardless of lifespan. You’ll learn how to do this in the next section.
Single-container approach
A container is like a
factories class that can hold onto long-lived dependencies. A container is a stateful version of a factories class.
A container class looks just like a factories class except with stored properties that hold onto long-lived dependencies
Ways to store properties in single container approach
You can either initialize constant stored properties during the container’s initialization or you can create the properties lazily if the properties use a lot of resources.
However, lazy properties have to be variables so constant properties are better by default.
Dealing with long-lived properties in
factory vs container approaches
To get a reference to a long-lived transitive dependency, a dependency factory method simply gets the dependency from a stored property.
in factory: add a parameters to factory methods.