I'm following the Singleton design pattern as described in this book (https://github.com/PacktPublishing/Go-Design-Patterns/blob/master/Chapter02/main.go) and I have this code in file "singleton2.go":
package singleton2
type Singleton interface {
AddOne() int
}
type singleton struct {
count int
}
//var instance = &singleton{}
var instance *singleton
func GetInstance() *singleton {
if instance == nil {
return new(singleton)
}
return instance
}
func (s *singleton) AddOne() int {
s.count++
return s.count
}
Then I have this test file (singleton2_test.go):
package singleton2
import "testing"
func TestGetInstance(t *testing.T) {
counter1 := GetInstance()
if counter1 == nil {
t.Fatal("expected pointer to Singleton after calling GetInstance(), not nil")
}
expectedCounter := counter1
currentCount := counter1.AddOne()
if currentCount != 1 {
t.Errorf("After calling for the first time to count, the counter must be 1 but it is %d", currentCount)
}
counter2 := GetInstance()
if counter2 != expectedCounter {
t.Errorf("Expected same instance in counter2 but it got a different instance.")
t.Logf("Got %v, want %v", counter2, expectedCounter)
}
currentCount = counter2.AddOne()
if currentCount != 2 {
t.Errorf("After calling AddOne() using second counter, the count must be 2, but it was %d", currentCount)
}
}
The problem is that tests always fail:
--- FAIL: TestGetInstance (0.00s)
singleton2_test.go:20: Expected same instance in counter2 but it got a different instance.
singleton2_test.go:21: Got &{0}, want &{1}
singleton2_test.go:26: After calling AddOne() using second counter, the count must be 2, but it was 1
FAIL
exit status 1
FAIL github.com/d3c3/design_patterns/singleton/singleton2 0.029s
Interestingly, if I change this line var instance *singleton
to this line var instance = &singleton{}
tests pass !? Why is that? IMO, it should work also with "var instance *singleton"
Can anyone explain this difference in behavior?
Follow the logic of your code and it's apparent where the problem is.
When you declare, but do not initialize the singleton (var instance *singleton
) then instance
is nil
. Calling GetInstance
evaluates instance == nil
as true and returns a new *singleton
every time it is called. That is why counter2
will never equal expectedCounter
- each call to GetInstance
is returning a new counter instance.
When you initialize the singleton instance (var instance = &singleton{}
) then calls to GetInstance
will return that singleton instance since it is not nil
.
I imagine you'd want to modify GetInstance
to something like this:
func GetInstance() *singleton {
if instance == nil {
instance = new(singleton)
}
return instance
}
EDIT
Although you didn't mention that you need this singleton to be thread-safe, user colminator correctly points out that instantiating a singleton this way can cause multiple go-routines to instantiate their own instance of the singleton, defeating the purpose of having one. Given how prolific concurrency is in Golang it's probably helpful to learn more! Check out the link he posted here.