Singleton
Ensure a single instance of a type exists using sync.Once for thread-safe lazy initialization.
Contributors
When to Use
- When exactly one instance of a type should exist
- For shared resources like database connections or configuration
- When lazy initialization is needed for expensive resources
When NOT to Use
- When dependency injection would make testing easier
- For simple state that can be passed as parameters
- When multiple instances don't cause problems
The Singleton pattern ensures only one instance of a type exists throughout the application’s lifetime. In Go, this is idiomatically implemented using sync.Once for thread-safe lazy initialization.
Implementation
package database
import (
"database/sql"
"sync"
)
type Database struct {
conn *sql.DB
}
var (
instance *Database
once sync.Once
)
// GetInstance returns the singleton database instance
func GetInstance() *Database {
once.Do(func() {
// This block executes only once, even with concurrent calls
conn, err := sql.Open("postgres", "connection-string")
if err != nil {
panic(err) // Or handle error appropriately
}
instance = &Database{conn: conn}
})
return instance
}
func (db *Database) Query(query string) error {
// Use the connection
_, err := db.conn.Exec(query)
return err
}
// Alternative: Package-level singleton with init
var Config *AppConfig
func init() {
Config = &AppConfig{
Host: "localhost",
Port: 8080,
}
}
type AppConfig struct {
Host string
Port int
}
Usage
package main
import (
"fmt"
)
func main() {
// Multiple calls return the same instance
db1 := database.GetInstance()
db2 := database.GetInstance()
fmt.Println(db1 == db2) // true - same pointer
// Use the singleton
db1.Query("SELECT * FROM users")
// Access package-level config singleton
fmt.Printf("Server: %s:%d\n", database.Config.Host, database.Config.Port)
}
Benefits
- Single Instance: Guarantees only one instance exists
- Thread-Safe:
sync.Onceensures safe concurrent initialization - Lazy Loading: Instance created only when first needed