Introduction to Dependency Containers: Part 2
Improving our Dependency Container with support for singletons
In the last article we learned about
Dependency Containers. We started building our own Dependency Container with basic support
to register and resolve dependencies.
In this short article we
will improve our Dependency Container by adding support for
singletons.
What are singletons?
Singletons are software components (e.g.
classes) from which we can create at most one instance. These types of
dependencies are useful when we don't need more than one instance and
we want to share the same instance between other software components.
The opposite of singleton dependencies are transient dependencies. Software components that are transient are instantiated every time they are requested from the dependency container. As a result, there can be many instances of transient software components.
This is the code we wrote last time:
function createDependencyContainer() {
const registrations = {};
function register(id, dependencies, factory) {
registrations[id] = {
id,
dependencies,
factory
};
}
function resolve(id) {
const registration = registrations[id];
if (!registration) {
throw new Error('No registration with ID ' + id + ' found.');
}
const { dependencies, factory } = registration;
if (dependencies.length === 0) {
return factory();
}
const resolvedDependencies = dependencies.map(dependency => resolve(dependency));
return factory(resolvedDependencies);
}
return {
register,
resolve
};
}
Now we are going to add an additional parameter to the "register" function. This parameter will specify whether the dependency is singleton or transient. It will be a boolean parameter and the value "true" will mark the dependency as singleton.
function register(id, dependencies, factory, singleton) {
registrations[id] = {
id,
dependencies,
factory,
singleton,
instance: undefined
};
}
In addition, we add two properties to each registration object:
- singleton - saves the value of the "singleton" parameter for future use.
- instance - references the instance if the dependency is singleton.
Now we have to modify our "resolve" function.
When we request
something from the Dependency Container we can check whether the
dependency is singleton and is already instantiated. If both
conditions are met, we can resolve it right away.
function resolve(id) {
const registration = registrations[id];
if (!registration) {
throw new Error('No registration with ID ' + id + ' found.');
}
const { singleton, instance } = registration;
if (singleton && instance) {
return instance;
}
}
If the conditions are not met, we have to resolve the dependency. This part is the same as before. If the requested software component has no dependencies we can create an instance right away. If it has dependencies, we have to resolve them first and then create an instance of what was requested.
function resolve(id) {
const registration = registrations[id];
if (!registration) {
throw new Error('No registration with ID ' + id + ' found.');
}
const { singleton, instance } = registration;
if (singleton && instance) {
return instance;
}
const { dependencies, factory } = registration;
const resolvedDependencies =
dependencies.length === 0
? []
: dependencies.map((dependency) => resolve(dependency));
const newInstance = factory(resolvedDependencies);
}
The final part before we return the created instance is to check whether the registration is singleton. If it is, we have to save the instance so that we can return the same instance for future requests.
function resolve(id) {
const registration = registrations[id];
if (!registration) {
throw new Error('No registration with ID ' + id + ' found.');
}
const { singleton, instance } = registration;
if (singleton && instance) {
return instance;
}
const { dependencies, factory } = registration;
const resolvedDependencies =
dependencies.length === 0
? []
: dependencies.map((dependency) => resolve(dependency));
const newInstance = factory(resolvedDependencies);
if (singleton) {
registration.instance = newInstance;
}
return newInstance;
}
See the following link for the full code:
https://github.com/ivan-georgiev-dev/introduction-to-dependency-containers/blob/part-2/script.js.
In the next part of this series we will add detection for cyclical
dependencies.
If you enjoy my articles, please consider supporting me.