Design Pattern (10) - Singleton Pattern Complete Tutorial - Ensuring Single Instance with Global Access
You can download the Design Pattern series code from this design_pattern repo.
Preface
In previous articles, we’ve learned about the Prototype Pattern, which is the second-to-last member of creational design patterns. Today we’ll introduce the last important member of creational patterns - the Singleton Pattern.
The Singleton Pattern is the most widely known creational design pattern that almost every software engineer encounters in object-oriented programming. Its core purpose is to ensure that a class can only have one instance in the entire system architecture and provide a global access point. This is very important for resource management and system performance optimization.
Requirements Background
Suppose we receive a specific development requirement: build an enterprise-level application that needs frequent and continuous data interaction with a database.
To ensure database connection efficiency and reasonable system resource utilization, we need to design a unified database connection management system. This system must be able to centrally manage all database operations while avoiding resource waste.
Initial Design Analysis (OOA)
After understanding the requirements, let’s conduct object-oriented analysis to design a basic database operation class:

In this initial design, the DatabaseClient
class contains standard CRUD (Create, Read, Update, Delete) four basic operation methods, plus a standard constructor to create DatabaseClient
instances.
This direct design approach looks simple, but it brings some key challenges in actual enterprise applications.
Problem Analysis (Forces)
When we use the above simple design in enterprise applications, we encounter the following key problems:
Resource Management Issues
In enterprise-level systems, each database connection consumes significant system resources (memory, network connections, threads). If we repeatedly create multiple DatabaseClient
instances in different places, it will cause resource exhaustion and performance degradation.
Data Consistency Issues
In multi-threaded environments, if different program modules use different database connection instances, it may lead to transaction state inconsistency, data synchronization problems, and other serious situations.
Performance Cost Issues
Database connection establishment and destruction are time-consuming operations. Frequently creating and closing connections not only affects system performance but also increases program complexity and maintenance difficulty.
These problems all point to the same solution direction: we need a unified database connection management mechanism.
Singleton Pattern Solution
After identifying the core problems, we can apply the Singleton Pattern to fundamentally solve these challenges. The core concept of the Singleton Pattern is to ensure that only one instance of a certain class can exist in the entire system.
Pattern Structure Design
Let’s first look at the standard UML structure of the Singleton Pattern:

Core Implementation Mechanism
The Singleton Pattern’s implementation mechanism is quite elegant and practical:
-
Lazy Initialization: Obtain instances through the
getInstance()
static method, not directly exposing the constructor. -
Instance State Check: Every time
getInstance()
is called, it checks whether the internalinstance
property isnull
. -
Conditional Creation: If
instance
isnull
, create a new instance and store it; if notnull
, directly return the existinginstance
.
This mechanism can absolutely guarantee that only one instance of this class exists from a thread safety perspective.
Applying to Database Connection Management
Now let’s apply the Singleton Pattern to DatabaseClient
design:

By introducing the Singleton Pattern, DatabaseClient
now has the following characteristics:
- Unified Entry Point: All database operations go through the same
DatabaseClient
instance - Resource Conservation: The entire system maintains only one database connection instance
- State Consistency: All database operations are performed on the same connection, ensuring data consistency
This design gives us a more robust and efficient database management architecture.
Program Implementation (OOP)
After understanding the design concept, let’s proceed with concrete code implementation. Let’s see how to implement the standard Singleton Pattern in Kotlin:
[DatabaseClient]
class DatabaseClient {
fun create(tableName:String, data: Map<String, Any>): Int {
return 0
}
fun read(tableName:String, conditions: Map<String, Any>): Int {
return 0
}
fun update(tableName:String, data: Map<String, Any>, conditions: Map<String, Any>): Int {
return 0
}
fun delete(tableName:String, conditions: Map<String, Any>): Int {
return 0
}
companion object {
var mInstance: DatabaseClient? = null
fun getInstance(): DatabaseClient {
if (mInstance == null) {
mInstance = DatabaseClient()
}
return mInstance!!
}
}
}
[Client]
fun main() {
val db = DatabaseClient.getInstance()
db.create("test", mapOf(Pair("test", "123")))
}
Standard Singleton Pattern Implementation
The above demonstrates the standard implementation of the Singleton Pattern. This implementation follows classic design pattern principles and is applicable to various programming languages.
Kotlin Language’s Elegant Implementation
It’s worth mentioning that Kotlin provides the object
keyword, allowing us to implement the Singleton Pattern more concisely:
[DatabaseClient]
object DatabaseClient {
fun create(tableName:String, data: Map<String, Any>): Int {
return 0
}
fun read(tableName:String, conditions: Map<String, Any>): Int {
return 0
}
fun update(tableName:String, data: Map<String, Any>, conditions: Map<String, Any>): Int {
return 0
}
fun delete(tableName:String, conditions: Map<String, Any>): Int {
return 0
}
}
[Client]
fun main() {
val db = DatabaseClient
db.create("test", mapOf(Pair("test", "123")))
}
Language Feature Advantages
Using Kotlin’s object
keyword, we get the following advantages:
- Automatic Thread Safety: Kotlin compiler guarantees thread-safe initialization of
object
- Lazy Loading: Only initializes the instance when first accessed
- Concise Syntax: No need to manually implement
getInstance()
method and null checks - Simplified Usage: Can access directly through class name, more intuitive
This is syntactic sugar that modern programming languages provide for common design patterns, allowing developers to use the Singleton Pattern more easily.
Series Summary
At this point, we’ve completed the learning journey of all Creational Design Patterns. From Factory Method’s object creation abstraction, to Abstract Factory’s product family management, to Builder’s complex object construction, Prototype’s object cloning technology, and finally Singleton’s global instance management.
In the next phase, we’ll begin exploring Structural Design Patterns. Structural patterns mainly focus on how to combine classes and objects to build larger structures while maintaining system flexibility and extensibility. Let’s look forward to the exciting content of upcoming patterns like Adapter, Bridge, Composite, and others!
Enjoy Reading This Article?
Here are some more articles you might like to read next: