mohuishou / go-design-pattern

golang design pattern go 设计模式实现,包含 23 种常见的设计模式实现,同时这也是极客时间-设计模式之美 的笔记
https://lailin.xyz/post/go-design-pattern.html
2.04k stars 339 forks source link

01 单例模式的问题 #2

Closed MrBear2018 closed 2 years ago

MrBear2018 commented 2 years ago

我测试的时候, 发现示例单例模式有问题

singleton.go

type Singleton struct {
}

var singleton *Singleton
var singleton2 *Singleton
var tag bool

func init()  {
    singleton = &Singleton{}
    singleton2 = &Singleton{}
}

func GetInstance() *Singleton{
    tag = !tag
    if tag{
        return singleton
    }
    return singleton2
}

singleton_test.go

import (
    . "github.com/smartystreets/goconvey/convey"
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestGetInstance(t *testing.T) {
    Convey("grpc server test", t, func() {
        a := GetInstance()
        b := GetInstance()
        assert.Equal(t, a, b)
    })
}

预期应该是不可以通过, 因为返回的是两个实例; 但是测试发现, 这样是能通过的,

错误的原因在于, singleton结构体是一个空结构体, 对于空结构体, go有特殊实现, 每次返回的地址都是全局变量 zerobase 的地址;

参考文档:https://segmentfault.com/a/1190000039315999

MrBear2018 commented 2 years ago

做了如下修改, 就正确了

type Singleton struct {
    tmp int
}

func TestGetInstance(t *testing.T) {
    Convey("grpc server test", t, func() {
        a := GetInstance()
        b := GetInstance()
        assert.Equal(t, true, a==b)
    })
}

注意, 这里的assert.Equal 也许要改一下形式, 用==来直接判定地址是否一样, 因为 assert.Equal 函数并不是比较引用(内存地址), 而是比较引用的值

mohuishou commented 2 years ago

单例本身是没问题的哈,这个是一个很简单的示例,每次获取的也只是同一个地址而已

就你的这个示例而言,只要里面加点字段就不会有你上面例子的这个问题了

xyling1024 commented 1 year ago

单例本身没有问题,但是你代码中 singleton 的申明是需要加上字段的;另外 assert.Equal 直接调用也是有问题的,不会判断地址值而是判断的对象值。参考:https://github.com/stretchr/testify/issues/1076 所以你的 singleton_test 并没有达到实际的测试效果,只是“负负得正”而已。一个正确的单测:

func TestGetInstance(t *testing.T) {
    s1 := GetInstance()
    s2 := GetInstance()
    s3 := GetLazyInstance()
    s4 := GetLazyInstance()
    assert.Equal(t, reflect.ValueOf(s1).Pointer(), reflect.ValueOf(s2).Pointer())
    assert.Equal(t, reflect.ValueOf(s3).Pointer(), reflect.ValueOf(s4).Pointer())
    assert.NotEqual(t, reflect.ValueOf(s2).Pointer(), reflect.ValueOf(s3).Pointer())
}
mohuishou commented 1 year ago

单例本身没有问题,但是你代码中 singleton 的申明是需要加上字段的;另外 assert.Equal 直接调用也是有问题的,不会判断地址值而是判断的对象值。参考:stretchr/testify#1076 所以你的 singleton_test 并没有达到实际的测试效果,只是“负负得正”而已。一个正确的单测:

func TestGetInstance(t *testing.T) {
  s1 := GetInstance()
  s2 := GetInstance()
  s3 := GetLazyInstance()
  s4 := GetLazyInstance()
  assert.Equal(t, reflect.ValueOf(s1).Pointer(), reflect.ValueOf(s2).Pointer())
  assert.Equal(t, reflect.ValueOf(s3).Pointer(), reflect.ValueOf(s4).Pointer())
  assert.NotEqual(t, reflect.ValueOf(s2).Pointer(), reflect.ValueOf(s3).Pointer())
}

的确是的哈