# 싱글톤 패턴 (Singleton Pattern) 이란?
[GoF의 디자인 패턴] 책에서의 싱글턴에 대한 요약은 이렇습니다.
1. 오직 한 개의 인스턴스만 갖도록 보장
외부 시스템과 상호작용 하면서 전역 상태를 관리하는 클래스 같은 경우,
인스턴스가 여러개로 만들어지는 것 자체로도 제대로 작동하지 않는 경우가 발생할 수 있습니다.
게임을 하는 플레이어의 상태, 혹은 게임 자체의 상태를 관리하는 역할의 클래스를 만들려 한다면,
이는 분명 게임에서 하나만 존재하여야 할것입니다.
하나만 가져야 한다는 규칙 자체를 컴파일 단계에서 강제하여 런타임에서 의도하지 않은 동작을
방지할 수 있습니다.
2. 전역 접근성을 제공
로깅, 컨텐츠 로딩, 게임 저장 등 여러 내부 시스템에서 전역적인 방향에서 게임에 대한 상태를 접근해서 얻고 싶을때에 대한 해결책을 제공합니다. 싱글톤은 어디서든 어떤 클래스에서든 누구든지, 싱글톤 클래스의 인스턴스에 접근하여 필요한 기능을 사용할 수 있습니다.
# 싱글톤을 왜 사용하는지?
위의 2가지 문제를 한번에 해결하는 간단한 방법이고, 버그의 여지가 거의 없기도 한 방법이기 때문입니다.
또 그 외에도 몇가지 장점은 있습니다.
[게임 프로그래밍 패턴] 책에서는 3가지의 장점을 더 알려주고 있습니다.
1. 한 번도 사용하지 않는다면 아예, 메모리조차 할당하지 않을 수 있습니다.
처음 사용될때 초기화를 시도하는 방법으로 싱글톤을 구현한다면, 게임 내에서 사용되지 않을 경우에 아예 초기화되지 않을 것이므로 메모리를 사용하지 않게 됩니다.
2. 런타임에 초기화 됩니다.
정적 멤버 변수는 컴파일 타임에 자동으로 초기화가 되는 문제가 있지만, 싱글턴은 최대한 늦게 필요한 시점에서 초기화를 시킬 수 있기 때문에, 해당 시점에는 싱글톤이 필요하는 정보를 안전하게 가져올 수 있고, 순환 참조의 문제만 없다면 다른 싱글톤끼리의 참조또한 어렵지 않습니다.
3. 싱글톤을 상속할 수 있습니다.
싱글톤 코드는 구현이 어느정도 고정되어 있으므로, 여러가지 관리자 클래스가 필요할때 하나의 싱글톤 부모 클래스를 상속함으로서 구현상의 생산성을 증대시킬 수 있습니다. 이 방법은 아래쪽에서 다룰 예정입니다.
# 싱글톤을 구현하는 방법에 대해
싱글톤을 구현하는 여러가지 방법을 알아보자.
1. 간단한 방법
이 방법은 아주 간단합니다. 클래스에 정적 변수로 자기 자신의 클래스에 대한 Instance를 만들고 Awake()함수에서 정적 변수를 초기화 하는 것으로 구현이 완료됩니다.
using UnityEngine;
public class MySingleton1 : MonoBehaviour
{
public static MySingleton1 Instance;
private void Awake()
{
Instance = this;
}
public void DoSomething()
{
print("DoSomething");
}
}
|
사용방법도 간단합니다.
using UnityEngine;
public class MyUser : MonoBehaviour
{
void Start()
{
MySingleton1.Instance.DoSomething();
}
}
|
문제의 여지가 많고 사용상에서 제약이 많은 코드입니다만, 간단하게 싱글톤 패턴을 구현했고,
원래에 해결 하고 싶었던 2가지 문제인, 하나의 인스턴스와 전역적인 사용성을 만족하는 가장 간단한 구현 코드 입니다. 게임이라는 프로젝트의 특성상 빈번하게 만들어질 가능성이 있는 패턴인 점에서 간결한 코드는 큰 장점입니다.
2. 여러가지를 고려하는 방법
1) 동기화에 대한 예외처리
2) 클래스만 있고 씬에서 해당 클래스 오브젝트가 없을때의 예외처리
3) DonDontDestroyOnLoad로 인해 씬이 재시작 되면 생기는 2개의 오브젝트 문제
4) 비대해진 코드를 압축하는 방법
를 고려하여서 제네릭으로 구현하는 방법이 있습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class MySingleton2<T> : MonoBehaviour where T : MonoBehaviour
{
// Check to see if we're about to be destroyed
private static bool m_ShuttingDown = false;
private static object m_Lock = new object();
private static T m_Instance;
/// <summary>
/// Access singleton instance through this propriety.
/// </summary>
public static T Instance
{
get
{
if (m_ShuttingDown)
{
Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
"' already destroyed. Returning null.");
return null;
}
lock (m_Lock)
{
if (m_Instance == null)
{
// Search for existing instance.
m_Instance = (T)FindObjectOfType(typeof(T));
// Create new instance if one doesn't already exist.
if (m_Instance == null)
{
// Need to create a new GameObject to attach the singleton to.
var singletonObject = new GameObject();
m_Instance = singletonObject.AddComponent<T>();
singletonObject.name = typeof(T).ToString() + " (Singleton)";
// Make instance persistent.
DontDestroyOnLoad(singletonObject);
}
}
return m_Instance;
}
}
}
private void OnApplicationQuit()
{
m_ShuttingDown = true;
}
private void OnDestroy()
{
m_ShuttingDown = true;
}
}
|
이러한 방식으로 구현하는 방법이 있으며, 여러가지 모두 고려된 방식입니다.
상속의 가능성을 없애버리고 코드가 콘크리트처럼 단단해지지만 유니티에서의 싱글톤을 구현하는데에서 고민이 되는 문제들을 가장 많이 한번에 해결하는 멋진 코드임에는 분명합니다.
아래에는 해당 클래스를 상속하여 싱글톤 패턴을 구현하는 방법입니다.
public class MyUser : MySingleton2<MyUser>
{
protected MyUser() { }
public void DoSomething()
{
}
}
|
해당 방법으로 구현한 모든 싱글톤클래스를 단 한줄에 똑같은 기능을 하도록 기대되는 구현을 할 수 있습니다.
# 정리하며
싱글톤이 무엇인지 왜 쓰면 좋은 것인지와 함께 구현 방법까지 알아보았습니다.
이 방법에도 분명한 단점이 있습니다.
싱글턴 패턴은 코드를 관리하기 어렵게 만들고 상태의존적이며 절차적이게 만드는 첫 단추가 되기도 합니다. 실제로 게임을 만드는데에 필요없는 안티패턴이라고 생각하는 의견도 많습니다만,
적절하게 쓰기만 한다면 간결함을 유지할 수 있고, 아주 편리한 개발패턴입니다.
'DEV > Unity Engine' 카테고리의 다른 글
[Unity] No Sprite Editor Window Error 스프라이트 에디터 에러 해결방법 (0) | 2022.02.20 |
---|---|
[Unity] How to import custom package? 다운받은 유니티 어셋 추가 방법 (0) | 2022.02.19 |
[Unity] 유니티 애니메이션의 샘플 레이트 설정이 안보이는 경우 (0) | 2020.03.10 |