DI Flashcards

1
Q

On-demand approach

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What are ephemeral dependencies?

A

they’re created and destroyed alongside the object-under-construction.

dependencies that can be instantiated at the same time as the object-under-construction

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

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

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Initializing ephemeral dependencies On-demand approach

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Pros of the on-demand approach

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Cons of the on-demand approach

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Instantiating dependencies on-demand is a decentralized approach that

A

doesn’t scale well.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

The factories approach is all about centralizing

A

dependency instantiation.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

One goal of creating a factories class is to make it possible

A

for consumers to create objects-under-construction without having to know how to build dependency graphs required to instantiate objects-under-construction.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Creating and getting transitive dependencies

A

To create an ephemeral transitive dependency, a dependency factory method can simply call another dependency factory included in the factories class.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

To get a reference to a long-lived transitive dependency, a dependency factory method should

A

include a parameter for the transitive dependency. By adding parameters, long-lived transitive dependencies can be provided to the dependency factory method.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Resolving protocol dependencies

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Object-under-construction factory methods

Object-under- construction factory methods look just like dependency factory methods. The only difference is

A

object-under-construction factory methods are called from the outside of a factories class, whereas dependency factory methods are called within a factories class.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Getting runtime values factory approach

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Injecting factories goal

A

The goal is to be able to unit test an object-under-construction without needing the factories class at all.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

You can give this power to objects-under-construction from the outside using one of two Swift features:

A

closures or protocols.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Using closures to give this power to objects-under-construction from the outside

A

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.

18
Q

Give this power to objects-under-construction from the outside by using protocols

Steps

Factory approach

A
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.
19
Q

Stateless class means

A

that class don’t have any stored properties and can have all methods static

20
Q

Pros of the factories approach

A

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.

21
Q

Cons of the factories approach

A

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.

22
Q

Single-container approach

A container is like a

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

23
Q

Ways to store properties in single container approach

A

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.

24
Q

Dealing with long-lived properties in

factory vs container approaches

A

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.

25
Q

Object-under-construction factory methods in container approach

A

Just like dependency factory methods from above, these factory methods also don’t need to have parameters for long-lived dependencies. This is a huge benefit for consumers because consumers don’t have to manage anything in order to create objects-under-construction.

26
Q

Substituting long-lived dependency implementations

A

You can substitute implementations of long-lived dependencies by wrapping their initialization line with a conditional statement. This is possible as long as the long- lived stored properties use a protocol type.

27
Q

Creating and holding a container

A

Unlike factories, you should only ever create one instance of a container.

28
Q

Pros of the single-container approach

A

A container can manage an app’s entire dependency graph. This removes the need for other code to know how to build object graphs.
Containers manage singletons; therefore, you won’t have singleton references floating in global space. Singletons can now be managed centrally by a container.
You can change an object’s dependency graph without having to change code outside the container class.

29
Q

Cons of the single-container approach

A

Putting all the long-lived dependencies and all the factory methods needed by an app into a single container class can result in a massive container class. This is the most common issue when using DI. The good news is that you can break this massive container up into smaller containers. You’ll read about this in the next section.

30
Q

Object scopes for DI containers

A

Every object has a lifetime. You want to explicitly design when objects come and go.

31
Q

Type of object scopes for DI containers

A

App scope
User scope
Feature scope
Interaction scope

32
Q

App scope

A

Traditional singletons fall under this scope. Objects in the app scope are created when the app launches and are destroyed when the app is killed. Typical dependencies you find in this scope include authentication stores, analytics trackers, logging systems, etc.

33
Q

User scope

A

User scope objects are created when a user signs in, and they’re destroyed when a user signs out. Some apps allow users to sign in to multiple accounts. In this case, the app could have multiple user scopes alive at the same time. Most dependencies, such as remote API’s and data stores, are usually found in this scope. This scope also typically contains more specific versions of dependencies found in the app scope. For instance, the app scope could have an anonymous analytics tracker while a user scope could have a user specific analytics tracker.

Scopes are very powerful because they help convert a bunch of mutable state into immutable state. For that reason, you can go even further with scopes by designing shorter lived scopes.

34
Q

Feature scope

A

Objects in a feature scope are created when the user navigates to a feature and are destroyed when the user navigates away. Feature scopes are handy when a feature needs to share data amongst many objects that make up the feature.

35
Q

Interaction scope

A

Objects in an interaction scope are created when a gesture is recognized and are destroyed when the gesture ends. This is handy when you are building a complex user interaction. This is an example of a very short-lived scope.

36
Q

Container hierarchy

A

A container manages the lifetime of the dependencies it holds. Because of this, each scope maps to a container.

This is how the dependencies that are in the user scope are all created and destroyed at the same time, because the scoped container owns these objects.

37
Q

Designing a container hierarchy

There’s one simple rule to building container hierarchies:

A

A child container can ask for dependencies from its parent container including the parent’s parents and so on, all the way to the root container. A parent container cannot ask for a dependency from a child container.

38
Q

The container hierarchy is

A

an object graph itself; therefore, you can use initializer injection to provide child containers with parent containers.

39
Q

Child container vs parent container

How they interact with each other?

A

The child container’s initializer needs to have a parameter for the parent container. The child container can then hold a reference to the parent container in a stored-property. This gives the child access to all the factory methods and stored properties in the parent container.

40
Q

Capturing data and handling optionals in container hierarchies

A

Besides managing the lifetime of dependencies, a container can also capture data model values. This is extremely helpful if the data model value is immutable for the lifetime of the container. Capturing data in a container is a way to convert mutable values into immutable values. This makes the code inside a container more deterministic because the logic does not have to consider a change in the captured value.

41
Q

Pros of the container hierarchy

A

Scoping allows you to design dependencies that don’t have to be singletons.

By capturing values in a scope, you can convert mutable values into an immutable values.

Container classes are shorter when you divide container classes into scoped container classes.

42
Q

Cons of the container hierarchy

A

Container hierarchies are more complex than a single-container solution. Developers that join your team might encounter a learning curve.

Even when containers are broken up into scoped containers, complex apps might still end up with really long container classes.