Есть ли Singleton в Golang?
- вторник, 5 декабря 2023 г. в 00:00:20
 

Давайте начнем с определения из Википедии.
“Одиночка (англ. 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‑ом и создать много экземпляров другими способами. А паттерн как раз и нужен для того, чтоб гарантированно исключить «не один экземпляр» в любом случае.