Menu
Coddy logo textTech

Паттерн Singleton

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 86 из 107.

Паттерн Singleton обеспечивает наличие только одного экземпляра типа на протяжении всей программы и предоставляет глобальную точку доступа к нему. Это полезно для общих ресурсов, таких как менеджеры конфигурации, подключения к базам данных или logger.

В Go мы реализуем Singleton с помощью переменной уровня package в сочетании с sync.Once, чтобы гарантировать потокобезопасную инициализацию:

package config

import "sync"

type Config struct {
    DatabaseURL string
    MaxRetries  int
}

var (
    instance *Config
    once     sync.Once
)

func GetInstance() *Config {
    once.Do(func() {
        instance = &Config{
            DatabaseURL: "localhost:5432",
            MaxRetries:  3,
        }
    })
    return instance
}

sync.Once гарантирует, что функция инициализации будет выполнена ровно один раз, даже если несколько горутин одновременно вызывают GetInstance(). Каждый последующий вызов возвращает тот же экземпляр без повторного выполнения кода инициализации.

cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 и cfg2 указывают на один и тот же экземпляр

Используйте Singleton умеренно. Хотя это удобно, он вводит глобальное состояние, которое может усложнить тестирование и скрыть зависимости. Подумайте, не будет ли внедрение зависимостей лучшей альтернативой для вашего случая.

challenge icon

Задание

Легко

Давайте создадим Singleton Logger, который гарантирует, что во всём вашем приложении существует only one экземпляр Logger! Это классический вариант использования паттерна Singleton. Вы хотите, чтобы все части вашей программы использовали одну и ту же конфигурацию и состояние Logger.

Вы организуете код в двух файлах:

  • logger.go: Implement потокобезопасный Singleton Logger.

    Создайте структуру Logger с двумя полями: Prefix (string) для префиксов сообщений журнала и MessageCount (int) для отслеживания того, сколько сообщений было logged.

    Используйте переменные уровня package с sync.Once, чтобы ensure, что only one instance когда-либо создаётся. Implement функцию GetLogger(), которая инициализирует Logger с префиксом default "[LOG]" и возвращает Singleton instance.

    Добавьте следующие методы в ваш Logger:

    • SetPrefix(prefix string) — изменяет префикс Logger
    • Log(message string) string — increments счётчик сообщений и возвращает formatted строку: [prefix] #[count]: [message]
    • GetCount() int — возвращает общее number сообщений, которые были logged
  • main.go: Продемонстрируйте, что multiple вызовы GetLogger() возвращают тот же instance.

    Прочитайте новое значение префикса, затем прочитайте count сообщений, за которым следует each сообщение для журналирования.

    Получите instance Logger, установите custom префикс, затем logged каждое сообщение и выведите результат. После журналирования всех сообщений снова получите instance Logger (чтобы доказать, что это тот же самый instance) и выведите общее number сообщений.

Будут предоставлены следующие входные данные:

  • Строка 1: Custom префикс для установки
  • Строка 2: Number сообщений
  • Следующие строки: Each сообщение для журналирования

Например, если даны:

[APP]
3
Server started
User connected
Request processed

Ваш вывод должен быть таким:

[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3

А если даны:

[DEBUG]
2
Initializing cache
Cache ready

Ваш вывод должен быть таким:

[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2

А если даны:

[ERROR]
1
Connection failed

Ваш вывод должен быть таким:

[ERROR] #1: Connection failed
Total messages logged: 1

Главная идея заключается в том, что независимо от того, сколько раз вы вызываете GetLogger(), вы always получаете один и тот же instance с общим состоянием. Счётчик сообщений сохраняется, потому что существует only one Logger!

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Читаем пользовательский префикс
	prefix, _ := reader.ReadString('\n')
	prefix = strings.TrimSpace(prefix)

	// Читаем количество сообщений
	countStr, _ := reader.ReadString('\n')
	countStr = strings.TrimSpace(countStr)
	count, _ := strconv.Atoi(countStr)

	// Читаем каждое сообщение
	messages := make([]string, count)
	for i := 0; i < count; i++ {
		msg, _ := reader.ReadString('\n')
		messages[i] = strings.TrimSpace(msg)
	}

	// TODO: Получить экземпляр логгера с помощью GetLogger()

	// TODO: Установить пользовательский префикс с помощью SetPrefix()

	// TODO: Залогировать каждое сообщение и вывести результат

	// TODO: Получить экземпляр логгера снова (чтобы доказать, что это тот же самый)
	// and print the total message count in format: "Total messages logged: X"
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go