싱글톤과 제네릭 사용시 주의점 ( Singleton Pattern , Generic )
싱글톤 ( Singleton ) 과 제네릭 ( Generic ) 조합은 이론상 편리해보이지만 Unity 나 C# 환경에서는 여러 가지 구조적 문제와 오용 가능성 때문에 일반적으로 권장되지 않는다. 아래에 그 이유를 단계별로 정리한다.
1. 목적이 충돌한다
- 싱글톤의 목적 : 전역에서 단 하나의 인스턴스만 유지
- 제네릭의 목적 : 여러 타입에 대해 재사용 가능하게 만든다
싱글톤은 하나만 존재해야 하는데 , 제네릭은 타입마다 다르게 존재하게 만든다.
결과적으로 타입별 싱글톤 여러 개가 생기는 구조가 되어 , 본래 싱글톤의 전역 통일성 ( Global Uniqueness ) 이 깨진다.
public class Singleton<T> where T : class, new()
{
private static T _instance;
public static T Instance => _instance ??= new T();
}
▲ Singleton<GameManager> , Singleton<AudioManager> 등으로 각각 따로 생성된다.
"전역 하나"가 아니라 "타입마다 하나씩" 생긴다.
2. Unity 에서는 MonoBehaviour 제약이 문제
Unity 에서 자주 쓰는 패턴은 MonoBehaviour 를 상속한 싱글톤이다.
제네릭으로 구현하면 다음과 같은 제약이 발생한다.
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
public static T Instance => _instance;
}
- new( ) 제약을 사용할 수 없다 ( MonoBehaviour 는 new 로 생성할 수 없기 때문이다 )
- 따라서 FindObjectOfType<T>( ) 등을 사용해야 하지만
이 경우 씬 로딩 시점이나 오브젝트 파괴 시점에 Null 문제가 자주 발생한다. - Unity 의 직렬화 ( Serialization ) 시스템이 제네릭 타입에 약하다
SerializeField 가 제대로 노출되지 않는 경우도 많다.
3. 코드의 의도가 모호해진다
제네릭 기반 싱글톤은 구조는 간결해 보여도 각 Manager 클래스의 정체가 흐려진다.
public class AudioManager : Singleton<AudioManager> { }
public class GameManager : Singleton<GameManager> { }
겉으로 보기에는 간단하지만 문제점이 있다
- 어떤 클래스가 진짜 전역 관리 역할을 하는지 모호해진다
- 상속 트리만 길어지고 , 구체적인 로직이 감춰져 있어 유지보수가 어려워진다
- 실수로 싱글톤 규칙을 깨도 ( Awake 중복 , DontDestroyOnLoad 누락 )
공통 기반 클래스가 이를 강제하지 못한다.
4. 테스트 및 확장성에 불리하다
제네릭 싱글톤을 사용하면 타입 의존성이 강해져서 Mock 객체나 테스트 코드 작성이 어렵다
var mock = new Mock<GameManager>(); // 불가능에 가깝다
인터페이스로 대체하거나 DI ( Dependency Injection ) 으로 전환하기가 힘들다
정리
| 구분 | 제네릭 싱글톤 사용 시 문제점 |
| 목적 충돌 | 싱글톤은 "하나" , 제네릭은 "여러 타입" |
| Unity 제약 | MonoBehaviour 생성 불가 , 직렬화 문제 |
| 유지보수성 | 구조는 간단하지만 의도가 모호해진다 |
| 테스트성 | Mock / DI 적용이 어렵다 |
권장하는 대안
- Unity 에서는 각 매니저별로 명시적으로 싱글톤 작성
( GameManager , AudioManager , UIManager 등 ) - 공통 로직만 별도의 BaseManager 나 ManagerHelper 로 분리
- DI 프레임워크 ( Zenject , Extenject 등 ) 를 사용해 의존성 관리
'🧊Unity Basic > 디자인패턴' 카테고리의 다른 글
| Adapter Pattern 핵심 코드 (0) | 2025.10.24 |
|---|---|
| Observer Pattern (0) | 2025.10.21 |
| Singleton Pattern (0) | 2025.10.20 |
| Adapter Pattern (0) | 2025.10.19 |
| Design Pattern (0) | 2025.10.18 |