Есть ли Singleton в Golang?
Давайте начнем с определения из Википедии.
“Одиночка (англ. Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.”
“Единственный экземпляр некоторого класса” означает что нет возможности написать код, в котором объект может быть скопирован или создан еще каким-либо способом.
В этом посте про "гарантирующий".
Рассмотрим общеизвестную реализацию. Поле в структуре - для лучшей визуализации результата.
type Singleton struct {
id int
}
var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{1}
})
return instance
}
Протестируем его.
func main() {
// first object
s1 := GetInstance()
fmt.Printf("type: %T, value: %v, ptr: %p\n", s1, s1, s1)
// second object. Copying
s2 := *s1
s2.id = 2
fmt.Printf("type: %T, value: %v, ptr: %p\n", s2, s2, &s2)
// third object
s3 := new(Singleton)
s3.id = 3
fmt.Printf("type: %T, value: %v, ptr: %p\n", s3, s3, s3)
// many different objects
for i := 4; i < 8; i++ {
s := Singleton{i}
fmt.Printf("type: %T, value: %v, ptr: %p\n", s, s, &s)
}
}
Результат:
type: *main.Singleton, value: &{1}, ptr: 0xc0000b4000
type: main.Singleton, value: {2}, ptr: 0xc0000b4018
type: *main.Singleton, value: &{3}, ptr: 0xc0000b4020
type: main.Singleton, value: {4}, ptr: 0xc0000b4030
type: main.Singleton, value: {5}, ptr: 0xc0000b4038
type: main.Singleton, value: {6}, ptr: 0xc0000b4040
type: main.Singleton, value: {7}, ptr: 0xc0000b4048
Вывод: создано неограниченное количество объектов!
Немного рассуждений.
Почему-то реализация паттерна в Go указывает программисту пользоваться функцией GetInstance().
А как следует из определения паттерна это должно быть не словесное правило, а сама реализация типа не должна позволять создать более одного экземпляра.
Если использовать словесные правила для программистов, то реализация паттерна могла бы быть и такой: "Не создавайте более одного экземпляра." И код писать не надо. )
Сравним с реализацией на языке С++.
Все конструкторы и оператор копирования или спрятаны в private
секции, или удалены. Или сразу оба способа.
В секции public
только один метод - GetInstance()
. Нет никаких вариантов создать более одного объекта.
class Singlеton
{
public:
static Singleton& GetInstance()
{
static Singleton theSingleInstance;
return theSingleInstance;
}
private:
Singleton(){}
Singleton(const Singleton& root) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Совсем другая картина в Go.
В тесте видно, что легитимными способами возможен не «единственный экземпляр некоторого класса».
«Для создания объекта используйте GetInstance()» — слабый аргумент. Программист может не знать что этот тип должен быть Singleton‑ом и создать много экземпляров другими способами. А паттерн как раз и нужен для того, чтоб гарантированно исключить «не один экземпляр» в любом случае.