
싱글톤 패턴은 객체 지향 프로그래밍에서 특정 클래스가 단 하나의 인스턴스를 생성하여 사용하기 위한 패턴이다. 생성자를 여러 번 호출하더라도 인스턴스가 하나만 존재하도록 보장하여 애플리케이션에서 동일한 객체 인스턴스에 접근할 수 있도록 한다.
커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 프로그램이 예상치 못한 결과를 낳을 수 있다. 따라서 객체를 필요할 때 마다 생성하는 것이 아닌 단 한 번만 생성하여 전역에서 이를 공유하고 사용할 수 있게 하기 위해 싱글톤 패턴을 사용한다.
객체 지향 설게 원칙 중 개방-폐쇄 원칙이란 것이 존재한다. 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되는데, 이 때 개방-폐쇄 원칙이 위배된다. 결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원할하게 진행할 수 없는 문제점이 발생한다.
또한, 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제도 발생할 수 있다. 따라서, 반드시 싱글톤이 필요한 상황이 아니면 지양하는 것이 좋다고 한다.(설계 자체에서 싱글톤을 원할하게 할 자신이 있으면 괜찮다.)
싱글톤 패턴은 게임 내에서 전역적으로 액세스할 수 있어야 하는 단일 리소스나 시스템(예: 게임 매니저, 오디오 매니저 또는 게임 설정 매니저)을 관리해야 할 때 주로 사양한다.
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
Singleton Instance = Singleton.Instance;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null)
{
var ob = new GameObject(typeof(T).Name);
instance = ob.AddComponent<T>();
}
}
return instance;
}
}
protected void Awake()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(this.gameObject);
}
else if (instance != this)
{
Destroy(this.gameObject);
}
}
}
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
public static T Instance { get; private set; }
private static System.Action<T> s_onAwake;
public static void WhenInstantiated(System.Action<T> action)
{
if (Instance != null)
action(Instance);
else
s_onAwake += action;
}
protected virtual void Awake()
{
if (!enabled)
return;
if (Instance != null)
{
Debug.LogWarning($"Another instance of Singleton {typeof(T).Name} is being instantiated, destroying...", this);
Destroy(gameObject);
return;
}
Instance = (T)this;
InternalAwake();
s_onAwake?.Invoke(Instance);
s_onAwake = null;
}
protected void OnEnable()
{
if (Instance != this)
Awake();
}
protected virtual void InternalAwake() { }
}