|From Dependency Injection, Principles, Practices, and Patterns by Steven van Deursen and Mark Seemann
This articles explains the Singleton Lifestyle: what it is and what it means for your code.
Take 37% off Dependency Injection, Principles, Practices, and Patterns. Just enter code fccseemann into the discount code box at checkout at manning.com.
The Singleton Lifestyle
The passing of time has a profound effect on most food and drink, but the consequences vary. Personally, we find 12-month-old Gruyère wine more interesting than 6-month-old Gruyère, but prefer our asparagus fresher than either of those. In many cases, it’s easy to assess the proper age of an item; but in certain cases, doing so becomes complex. This is most notable when it comes to wine.
Wines tend to get better with age—until they suddenly become too old and lose most of their flavor. This depends on many factors, including the origin and vintage of the wine. Although wines interest us, we don’t ever expect we’ll be able to predict when a wine will peak. For that, we rely on experts: books at home and sommeliers at restaurants. They understand wines better than we do, so we happily let them take control.
Letting go of control is a key concept in Dependency Injection. This stems from the Inversion of Control principle, where you delegate control of your dependencies to a third party, but it also implies more than just letting someone else pick an implementation of a required abstraction. When you allow a Composer to supply a dependency, you must also accept that you can’t control its lifetime.
DEFINITION: Composer is a unifying term to refer to any object or method that composes dependencies. It’s an important part of the Composition Root. The Composer is often a DI Container, but it can also be any method that constructs object graphs manually (using Pure DI)
Just as the sommelier intimately knows the contents of the restaurant’s wine cellar and can make a far more informed decision than we can, we should trust the Composer to be able to control the lifetime of dependencies more efficiently than the consumer. Composing and managing components is its single responsibility. A Composer can apply different lifetime strategies. These strategies are formalized in the form of Lifestyles.
DEFINITION: A Lifestyle is a formalized way of describing the intended lifetime of a dependency.
One of the Lifestyles to choose from is the Singleton Lifestyle. Its name is both clear and somewhat confusing at the same time. It makes a lot of sense because the resulting behavior is similar to the Singleton design pattern, but the structure is different.
DEFINITION: Within the scope of a single Composer, there will be only one instance of a component with the Singleton Lifestyle. Each and every time a consumer requests the component, the same instance is served.
With both the Singleton Lifestyle and the Singleton design pattern there is only one instance of a dependency, but the similarity ends there. The Singleton design pattern provides a global point of access to its instance, which is very similar to the Ambient Context anti-pattern. A consumer however, can’t access a Singleton-scoped dependency through a static member, and if we ask two different Composers to serve us an instance, we’ll get two different instances.
WARNING: Don’t confuse the Singleton lifestyle with the Singleton design pattern.
Because only a single instance is in use, the Singleton Lifestyle generally consumes a minimal amount of memory.
When to use the Singleton lifestyle
Use the Singleton Lifestyle when possible. There are two main issues preventing you from using Singleton:
- When a component isn’t thread-safe. Because the Singleton instance is shared among potentially many consumers, it must be able to handle concurrent access.
- When one of the component’s dependencies has a lifetime that is expected to be shorter. Making the component a Singleton would keep the dependency alive for too long. Such dependency in that case becomes a Captive Dependency.
All stateless services are by definition thread-safe, as are immutable types and obviously classes specifically designed to be thread-safe. In these cases, there is no reason not to configure them as Singletons.
In addition to the argument for efficiency, some dependencies may work as intended only if they’re shared. For example, this is the case for in-memory caches. In such case, it’s essential that the implementations are thread-safe.
The Singleton Lifestyle is one of the easiest Lifestyles to implement. All it requires is that you keep a reference to the object and serve the same object every time it’s requested. The instance doesn’t go out of scope until the Composer goes out of scope. When that happens, the Composer should dispose of the object if it’s a disposable type.
That’s all for this article. If you want to learn more about the book, check it out on liveBook here.
You can also see other articles on common DI-related topics:
Service Locator Anti-Pattern
Ambient Context Anti-Pattern