← Назад к вопросам

Что такое паттерн Singleton? Как его реализовать в Unity?

2.0 Middle🔥 201 комментариев
#Паттерны проектирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерн Singleton (Одиночка)

Singleton — это порождающий паттерн проектирования, который гарантирует, что у класса существует только один экземпляр в рамках всего приложения, и предоставляет к нему глобальную точку доступа. В контексте Unity это особенно полезно для создания менеджеров (GameManager, AudioManager, UIManager), сервисов или контроллеров, которые должны быть в единственном числе и доступны из любой части кода.

Зачем использовать Singleton в Unity?

  • Централизованное управление: Например, один AudioManager для всех звуков в игре.
  • Глобальный доступ: Не нужно передавать ссылки через сцены или использовать FindObjectOfType() постоянно.
  • Ленивая инициализация: Экземпляр создается только при первом обращении.
  • Контроль над состоянием: Гарантия, что важные данные не дублируются.

Реализация Singleton в Unity

Существует несколько подходов. Рассмотрим наиболее надежные.

1. Базовый Singleton для MonoBehaviour

Подходит для менеджеров, которые должны существовать в сцене и могут использовать корутины, события MonoBehaviour.

using UnityEngine;

public class GameManager : MonoBehaviour
{
    // Статическая публичная ссылка на единственный экземпляр.
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        // Проверяем, существует ли уже экземпляр.
        if (Instance != null && Instance != this)
        {
            // Если да, уничтожаем этот дубликат.
            Destroy(this.gameObject);
        }
        else
        {
            // Если нет, сохраняем этот экземпляр.
            Instance = this;
            // Делаем объект неуничтожаемым при загрузке новой сцены.
            DontDestroyOnLoad(this.gameObject);
        }
    }

    // Пример метода менеджера.
    public void CompleteLevel()
    {
        Debug.Log("Level Completed!");
    }
}

Использование: GameManager.Instance.CompleteLevel();

2. Потокобезопасный Singleton для обычных C# классов

Используется для не-MonoBehaviour сервисов, логики, которая не зависит от игрового цикла Unity.

using System;

public class ScoreService
{
    // Приватный статический экземпляр с ленивой инициализацией.
    private static readonly Lazy<ScoreService> lazyInstance =
        new Lazy<ScoreService>(() => new ScoreService());

    // Публичное свойство для доступа.
    public static ScoreService Instance => lazyInstance.Value;

    // Закрытый конструктор предотвращает создание экземпляров извне.
    private ScoreService()
    {
        // Инициализация.
        CurrentScore = 0;
    }

    public int CurrentScore { get; private set; }

    public void AddPoints(int points)
    {
        CurrentScore += points;
    }
}

Использование: ScoreService.Instance.AddPoints(100);

3. Универсальный шаблон (Generic Singleton)

Позволяет легко создавать синглтоны для разных менеджеров, избегая дублирования кода.

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Component
{
    private static T _instance;

    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                // Пытаемся найти существующий экземпляр в сцене.
                _instance = FindObjectOfType<T>();
                if (_instance == null)
                {
                    // Если не нашли, создаем новый GameObject.
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    _instance = obj.AddComponent<T>();
                }
            }
            return _instance;
        }
    }

    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

Использование: Наследуйте ваш менеджер от этого класса.

public class AudioManager : Singleton<AudioManager>
{
    public void PlaySound(string clipName)
    {
        // Реализация...
    }
}
// Вызов: AudioManager.Instance.PlaySound("Jump");

Важные предостережения и лучшие практики

  • Не злоупотребляйте: Singleton — это, по сути, глобальная переменная. Чрезмерное использование приводит к сильной связанности кода и затрудняет тестирование.
  • Порядок инициализации: В Unity порядок вызова Awake() не гарантирован. Если один синглтон обращается к другому в Awake(), может возникнуть ошибка. Используйте Start() или ручную инициализацию.
  • Многопоточность: Базовая реализация для MonoBehaviour не потокобезопасна. Для сложных многопоточных операций (например, в сервисах данных) используйте Lazy<T> или lock.
  • Сцены и DontDestroyOnLoad: Будьте осторожны, используя DontDestroyOnLoad. Убедитесь, что вам действительно нужен один менеджер на всю игру, а не отдельный экземпляр на уровне.
  • Альтернативы: Рассмотрите другие подходы, такие как Dependency Injection (через Zenject/Extenject или VContainer) или передача ссылок через ScriptableObject Events. Они делают зависимости более явными и код более гибким.

Вывод

Singleton — мощный и простой паттерн для управления уникальными сервисами в Unity. Его основная сила — в простоте реализации и глобальной доступности. Однако его главный недостаток — маскировка зависимостей и усложнение модульного тестирования. Используйте его осознанно, в первую очередь для высокоуровневых менеджеров игры, и старайтесь разделять логику на более мелкие, тестируемые компоненты. Для MonoBehaviour-классов предпочтительна первая или третья реализация, а для "чистой" логики — вторая, с Lazy<T>.