Simplifying Design Patterns : Singleton

Introduction
Design patterns are proven solutions to common software design problems. In this article, we’ll simplify the Singleton pattern—explaining how it works, when to use it, and how to implement it with clear examples. The examples are inspired by Refactoring Guru and Dive Into Design Patterns (Alexander Shvets), distilled to their essence.
How Does the Singleton Pattern Work?
The Singleton is a creational design pattern that ensures a class has only one instance and provides a global access point to it.
Example Scenario: A Restaurant Kitchen
Imagine a restaurant where:
- There is a
kitchen
that needs only oneoven
for everydish
- Instead of starting a new
oven
every time adish
is cooked, thekitchen
reuses the existing one. - If the
oven
isn’t on yet, it gets started once. After that, every dish uses the sameoven
.
Everytime a dish
is about to be cooked and the oven
is called to start, it must check if it is already instantiated, if it is then the instance is returned if not the constructor must be called.
When to Use the Singleton Pattern?
Use this pattern when:
- You have a class in your program that should just have one single instance available, like a database object.
- You need strict control over global variables
How to Implement the Singleton Pattern
Step-by-Step (Restaurant Example)
-
Define a Private Static Instance
- The class holds its only instance in a private static field:
private static Oven _instance;
-
Lock the Constructor
- Make the constructor private to block external instantiation:
private Oven() { IsOn = false; // Initialization logic }
-
Create a Controlled Access Method
- Provide a public static method (
GetInstance
) with lazy initialization:
public static Oven GetInstance() { if (_instance == null) { _instance = new Oven(); // Creates instance only on first call } return _instance; }
- Provide a public static method (
-
Add Thread Safety (Optional)
- For multi-threaded kitchens, use
lock
orLazy
:
private static readonly object _lock = new object(); // ... lock (_lock) { if (_instance == null) _instance = new Oven(); }
- For multi-threaded kitchens, use
-
Modern Approach: Use
Lazy
(Recommended)- Replace manual checks with C#’s built-in lazy initialization:
private static readonly Lazy<Oven> _lazyOven = new Lazy<Oven>(() => new Oven()); public static Oven Instance => _lazyOven.Value;