Design Pattern: Singleton Pattern in Go

Singleton pattern is used to ensure that only an instance of a class is created. Each time an attempt to create an instance of the class will return the same instance.

Implementing the Singleton pattern in Go is slightly different from Java, PHP, and TypeScript. Let’s take a look at a few examples of Singleton design pattern in Go.

Implementation

There are a few points that we need to consider while implementing Singleton pattern in Go.

Common Key Points

These are the common key points for implementing Singleton pattern in Go.

  • Use a struct to define properties required in the singleton instance.
  • Use a variable to store instance of the singleton.
  • Create a function to generate the singleton instance.
  • In the function check if an instance already exists or not. Return the existing instance if there is an existing one.
  • If there is no existing instance then create a new one and return it.

Step #1: Simplest Singleton Implementation

This is the simplest approach we can take to implement Singleton pattern.

  • Create a struct named “singleton”.
  • Declare a variable named “singletonInstance” of type “*singleton”, so that it can hold a reference(pointer) to the singleton instance.
  • Create a function named “GetInstance”. This will accept any required parameter, and return “*singleton”.
  • In the function check if a singleton is already generated or not. If not then generate a new instance.
package main

import (
	"fmt"
	"log"
)

/** ---------- Singleton Implementation Start ---------- **/

// Define a struct for singleton
type singleton struct {
	logPath string // Some data for the struct
}

// Declare an variable to hold the singeton instance
var singletonInstance *singleton

// Define a function that is responsible for generating singleton instance
func GetInstance(logPath string) *singleton {
	// If there is no existing instance then generate one
	if singletonInstance == nil {
		log.Println("Generating new singleton instance")

		singletonInstance = &singleton{logPath}
	}

	return singletonInstance
}

/** ---------- Singleton Implementation End ---------- **/

// Check the result in main
func main() {
	// Generate one instance
	singletonInstance1 := GetInstance("/path/abc")

	fmt.Println(singletonInstance1.logPath)

	// Try to generate another instance
	singletonInstance2 := GetInstance("/some/changed/path")

	fmt.Println(singletonInstance2.logPath)

	// Check if the 2 instances are same or not
	if singletonInstance1 == singletonInstance2 {
		fmt.Println("Singleton Instance 1 and 2 are same instance") // This is the expected output
	} else {
		fmt.Println("Singleton Instance 1 and 2 are different")
	}
}

Output of the code will be as below:

/path/abc
/path/abc

Singleton Instance 1 and 2 are same instance

Step #2: Use sync.Once

The instance generation part can be improved. We need to ensure thread safety for the instance generation part, as 2 threads can try to generate an instance at the same time.

For thread safety we can either generate the instance in “init()”, or we can use the “sync.Once”.

In the case of “init()” the instance will be generated, no matter if an instance is required or not. So, it is better to use “sync.Once”.

The following code is a small improvement over the previous code.

  • Import package “sync”.
  • Create a variable named “once” of type “sync.once”.
  • Wrap the singleton instance generating part inside “once.Do(func() { // generate new instance })”.

This will ensure the code responsible for the instance generation, is only executed once. So checking if the instance already exists, is not required in this case.

package main

import (
	"fmt"
	"log"
	"sync"
)

/** ---------- Singleton Implementation Start ---------- **/

// Define a struct for singleton
type singleton struct {
	logPath string // Some data for the struct
}

// Declare an variable to hold the singeton instance
var singletonInstance *singleton

// Declare once for using while generating singleton instance
var once sync.Once

// Define a function that is responsible for generating singleton instance
func GetInstance(logPath string) *singleton {
	// Generate the instance only once
	once.Do(func() {
		log.Println("Generating new singleton instance")

		singletonInstance = &singleton{logPath}
	})

	return singletonInstance
}

/** ---------- Singleton Implementation End ---------- **/

// Check the result in main
func main() {
	// Generate one instance
	singletonInstance1 := GetInstance("/path/abc")

	fmt.Println(singletonInstance1.logPath)

	// Try to generate another instance
	singletonInstance2 := GetInstance("/some/changed/path")

	fmt.Println(singletonInstance2.logPath)

	// Check if the 2 instances are same or not
	if singletonInstance1 == singletonInstance2 {
		fmt.Println("Singleton Instance 1 and 2 are same instance") // This is the expected output
	} else {
		fmt.Println("Singleton Instance 1 and 2 are different")
	}
}

It will generate the same output. Output of the above code will be as below:

/path/abc
/path/abc

Singleton Instance 1 and 2 are same instance

Examples

Let’s take a look at a few examples of Singleton pattern implementation in Go.

Example #1: Database Connection

Let’s implement the singleton pattern for representing a database connection.

Singleton Implementation

Follow the steps below to implement the connection struct as Singleton –

  • Create a file named “connection.go”.
  • Declare a struct named “connection” and use it to represent a database connection.
  • Declare a variable named “dbConnectionInstance” of the type connection pointer(*connection).
  • Define a function named “GetDbInstance”. Accept the required parameters for a database connection, and return a reference to the connection(*connection).
  • Use “once.Do” in the “GetDbInstance” function to make sure that the instance is generated only once.
  • Declare an interface named “DbConnection”. Declare a few utility functions for the database connection, and write the function definition. This will make sure the known interface while interacting with the connection.  
// connection.go

package main

import (
	"fmt"
	"log"
	"sync"
)

// Struct for database connection information
type connection struct {
	host     string
	port     string
	username string
	password string
}

// Interface for connection related functions
type DbConnection interface {
	PrintInfo()
	ExecuteQuery(query string)
}

// Store connection reference
var dbConnectionInstance *connection

// To make sure instance generation code is executed only once
var once sync.Once

// Function for generating and getting database connection instance
func GetDbInstance(host, port, username, password string) *connection {
	// Use once.Do to make sure the instance generation code is executed only once
	once.Do(func() {
		log.Println("Creating a new database instance")

		// Create a new instance and store it in the dbConnectionInstance variable
		dbConnectionInstance = &connection{host, port, username, password}
	})

	return dbConnectionInstance
}

// Utility function for demonestrating database connection information -- Dummy function
func (conn *connection) PrintInfo() {
	fmt.Println("host:", conn.host)
	fmt.Println("post:", conn.port)
	fmt.Println("username:", conn.username)
	fmt.Println("password:", conn.password)
}

// Utility function for database -- Dummy function
func (conn *connection) ExecuteQuery(query string) {
	fmt.Println("Executing: ", query)
}

Demo

To demo the singleton pattern implementation use the following code.

  • Use “GetDbInstance” and create an instance with the required parameters.
  • Create another instance with some different parameters.
  • Check if those 2 instances are the same or not.

Because of the Singleton implementation, those 2 instances will be the same.

// main.go

package main

import "fmt"

func main() {
	// Create fist instance
	dbInstance1 := GetDbInstance("localhost", "3306", "root", "complex#password")
	dbInstance1.PrintInfo()

	// Create second instance
	dbInstance2 := GetDbInstance("localhost2", "2222", "root2", "complex#password2")
	dbInstance2.PrintInfo()

	// Compare the 2 instances. Both should be same, as both should refernece to the same instance
	if dbInstance1 == dbInstance2 {
		fmt.Println("dbInstance 1 and 2 are same instance") // This is the expected output
	} else {
		fmt.Println("dbInstance 1 and 2 are different")
	}

}

Output

The output of the above code will be as below.

host: localhost
post: 3306
username: root
password: complex#password

host: localhost
post: 3306
username: root
password: complex#password

dbInstance 1 and 2 are same instance

Tests

Let’s create test cases for testing the Singleton pattern implementation.

  • Create a file named “connection_test.go”.
  • Create multiple instances of connection, using “GetDbInstance” function.
  • Check if all the instances of connection are the same, and also perform a deep compare of those instances.
// connection_test.go

package main

import (
	"reflect"
	"testing"
)

func TestGetDbInstance(t *testing.T) {
	// Create 2 instances with different param
	connection1 := GetDbInstance("localhost", "1", "root1", "complex#password1")
	connection2 := GetDbInstance("localhost2", "2", "root2", "complex#password2")

	// Check if the 2 instances are the same or not
	if connection1 != connection2 {
		t.Error("Singleton database connnection does not return same instance")
	}

	// Compare with deep equal
	if !reflect.DeepEqual(connection1, connection2) {
		t.Error("Singleton instances has different values")
	}
}

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Singleton pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.