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

Когда используется Subject?

1.8 Middle🔥 102 комментариев
#Другое

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

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

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

📌 Общее предназначение Subject в контексте Unity

В Unity и C# разработке термин Subject чаще всего относится к паттерну Наблюдатель (Observer) в рамках реактивного программирования, реализованного в библиотеке UniRx (Reactive Extensions для Unity). Subject выступает в роли одновременно Observable (источника событий) и Observer (подписчика), позволяя мультикастить события множеству подписчиков.

🎯 Основные сценарии использования Subject в Unity

1. Централизованная система событий

Когда необходимо оповестить множество независимых систем об изменении состояния без жестких связей.

using UniRx;
using UnityEngine;

public class GameEventSystem : MonoBehaviour
{
    // Subject как глобальная шина событий
    public static Subject<Unit> OnPlayerDeath = new Subject<Unit>();
    public static Subject<int> OnScoreChanged = new Subject<int>();
    
    private void PlayerDied()
    {
        // Отправка события всем подписчикам
        OnPlayerDeath.OnNext(Unit.Default);
    }
    
    private void AddScore(int points)
    {
        OnScoreChanged.OnNext(points);
    }
}

2. Мостик между императивным и реактивным кодом

Subject идеален, когда событие возникает в императивном коде (например, в MonoBehaviour), но должно обрабатываться реактивно.

public class PlayerHealth : MonoBehaviour
{
    private Subject<float> _healthChanged = new Subject<float>();
    public IObservable<float> HealthChanged => _healthChanged;
    
    private float _currentHealth = 100f;
    
    public void TakeDamage(float damage)
    {
        _currentHealth -= damage;
        // Преобразование императивного вызова в потоковое событие
        _healthChanged.OnNext(_currentHealth);
        
        if (_currentHealth <= 0)
            _healthChanged.OnCompleted(); // Завершение потока
    }
}

3. Кэширование и ретрансляция последнего значения

BehaviorSubject и ReplaySubject особенно полезны для хранения и передачи последнего состояния новым подписчикам.

public class SettingsManager : MonoBehaviour
{
    // BehaviorSubject хранит текущее значение и передает его новым подписчикам
    private BehaviorSubject<GraphicsQuality> _qualitySetting = 
        new BehaviorSubject<GraphicsQuality>(GraphicsQuality.Medium);
    
    public IObservable<GraphicsQuality> QualityObservable => _qualitySetting;
    
    public void SetQuality(GraphicsQuality quality)
    {
        _qualitySetting.OnNext(quality);
    }
    
    // Новый UI элемент сразу получит текущее качество графики
    private void Start()
    {
        _qualitySetting.Subscribe(quality => 
            Debug.Log($"Current quality: {quality}"));
    }
}

4. Мультикастинг событий ввода

Обработка пользовательского ввода с несколькими потребителями.

public class InputController : MonoBehaviour
{
    private Subject<Vector2> _moveInput = new Subject<Vector2>();
    private Subject<bool> _jumpInput = new Subject<bool>();
    
    public IObservable<Vector2> MoveStream => _moveInput;
    public IObservable<bool> JumpStream => _jumpInput;
    
    void Update()
    {
        Vector2 move = new Vector2(
            Input.GetAxis("Horizontal"),
            Input.GetAxis("Vertical")
        );
        
        if (move.magnitude > 0.1f)
            _moveInput.OnNext(move.normalized);
        
        if (Input.GetButtonDown("Jump"))
            _jumpInput.OnNext(true);
    }
}

⚠️ Критически важные рекомендации по использованию

Когда НЕ использовать обычный Subject:

  • Если нужна только подписка на события - используйте IObservable
  • При работе с асинхронными операциями - рассмотрите AsyncSubject
  • Для горячих observable с одним источником - используйте Observable.Create

Типы Subject и их специализация:

Тип SubjectНазначениеОсобенность
Subject<T>Базовый мультикастингНе кэширует значения
BehaviorSubject<T>Состояние приложенияКэширует последнее значение
ReplaySubject<T>История событийКэширует N последних значений
AsyncSubject<T>Асинхронные операцииОтдает только последнее значение при завершении
// Пример специализированных Subject
public class AdvancedUsage : MonoBehaviour
{
    // Для настроек, где важно текущее значение
    private BehaviorSubject<Resolution> _resolution = 
        new BehaviorSubject<Resolution>(Screen.currentResolution);
    
    // Для истории чата (последние 100 сообщений)
    private ReplaySubject<ChatMessage> _chatHistory = 
        new ReplaySubject<ChatMessage>(bufferSize: 100);
    
    // Для загрузки ресурса
    private AsyncSubject<Texture2D> _textureLoaded = 
        new AsyncSubject<Texture2D>();
}

🏆 Лучшие практики

  1. Инкапсуляция: Всегда предоставляйте Subject как IObservable через свойство только для чтения
  2. Завершение потоков: Вызывайте OnCompleted() или OnError() когда поток больше не нужен
  3. Отписка: Используйте CompositeDisposable или AddTo() для автоматической отписки
  4. Тестируемость: Subject упрощает unit-тестирование событийных систем
public class SafeSubjectUsage : MonoBehaviour
{
    private Subject<int> _internalSubject = new Subject<int>();
    private CompositeDisposable _disposables = new CompositeDisposable();
    
    // Публичный интерфейс только для чтения
    public IObservable<int> ValueStream => _internalSubject;
    
    void Start()
    {
        // Автоматическая отписка при уничтожении объекта
        _internalSubject
            .Where(x => x > 10)
            .Subscribe(x => Debug.Log(x))
            .AddTo(_disposables); // Критически важно для Unity!
    }
    
    void OnDestroy()
    {
        _disposables.Dispose();
        _internalSubject.OnCompleted();
    }
}

Subject в Unity - это мощный инструмент для создания отзывчивых, декомпозированных систем, но требует понимания реактивного программирования и ответственности за управление жизненным циклом потоков данных.