■ 제네릭 싱글톤 (Generic Singleton)
○ 특징과 용도
- 제네릭 싱글톤은 특정 타입을 사용하여 싱글톤 인스턴스를 생성하는 디자인 패턴
- 제네릭을 사용하면 여러 타입에 대해 동일한 싱글톤 패턴을 구현할 수 있어, 다양한 타입에 유연하게 대응할 수 있는 싱글톤을 구현하는 데 유용함
- 보통 C# 같은 언어에서 제네릭 싱글톤은 static 키워드를 이용해 구현되며, 이를 통해 스레드 안전성을 확보할 수 있음
○ 제네릭 싱글톤 구현 예시
public class Singleton<T> where T : class, new()
{
private static readonly Lazy<T> _instance = new Lazy<T>(() => new T());
public static T Instance => _instance.Value;
// 생성자를 private으로 설정하여 외부에서 인스턴스 생성을 방지
private Singleton() { }
}
Lazy<T>
.NET에서 제공하는 Lazy를 사용하여 스레드 안전성을 갖춘 지연 초기화된 인스턴스를 생성함
Instance
Lazy.Value는 첫 접근 시 초기화되며 이후에는 캐시된 인스턴스를 반환함
where T : class, new()
제네릭 타입 T는 클래스이어야 하며 매개변수가 없는 기본 생성자가 있어야 함
private Singleton()
외부에서 생성자를 통해 인스턴스를 생성하는 것을 방지함
○ 사용 예시
public class MyClass { }
var instance1 = Singleton<MyClass>.Instance;
var instance2 = Singleton<MyClass>.Instance;
// instance1과 instance2는 동일한 참조를 가짐
Console.WriteLine(object.ReferenceEquals(instance1, instance2)); // true
- 제네릭 싱글톤은 타입별로 유일한 인스턴스를 유지하기 때문에 위와 같은 형태로 사용할 수 있음
- 이렇게 하면 특정 타입의 싱글톤 인스턴스만 필요할 때마다 생성할 수 있음
- 필요할 경우 새로운 타입의 싱글톤 인스턴스도 생성할 수 있는 유연성을 확보할 수 있음
■ 연습 : 싱글톤 베이스와 매니저 스크립트
깃허브 링크
○ SingletoneBase
using UnityEngine;
public class Singletone<T> : MonoBehaviour where T : Component
{
// 싱글톤
private static T instance;
public static T Instance
{
get
{
// 싱글톤 만들기
// 1. instance - null 체크
if(instance == null)
{
// 없다면 새로운 싱글톤 만들기
// 2. 예외처리 - 혹시 씬에 싱글톤이 있는지
instance = (T)FindObjectOfType(typeof(T));
if (instance == null)
{
// 3. 새로운 오브젝트 만들기
string tName = typeof(T).ToString(); // 오브젝트 이름 정하기
var singletoneObj = new GameObject(tName); // 타입 이름대로 지정되어 생성됨
// 4. 컴포넌트를 추가 <- T 추가
// 5. instance 할당
instance = singletoneObj.AddComponent<T>();
}
}
// 있다면 instance 리턴
return instance;
}
}
private void Awake()
{
if(instance != null)
DontDestroyOnLoad(instance);
}
public void Init()
{
}
public void Release()
{
}
}
○ GameManager
public class GameManager : Singletone<GameManager>
{
public Action OnGameStart;
public Action OnGameEnd;
private void Start()
{
UIManager.Instance.OpenEndUI();
}
}
○ AudioManager
public class AudioManager : Singletone<AudioManager>
{
AudioSource audioSource;
private void Start()
{
audioSource = GetComponent<AudioSource>();
}
public void PlayBGM()
{
if(audioSource != null)
audioSource.Play();
}
}
○ UIManager
public class UIManager : Singletone<UIManager>
{
private void Start()
{
Debug.Log(GameManager.Instance);
GameManager.Instance.OnGameStart += OpenStartUI;
GameManager.Instance.OnGameEnd += OpenEndUI;
}
public void OpenStartUI()
{
Debug.Log(message: "GameStart");
}
public void OpenEndUI()
{
Debug.Log(message: "GameEnd");
}
}
○ ObjectPoolManager
public class ObjectPoolManager : Singletone<ObjectPoolManager>
{
// [구현 사항 4]
// 구현사항 1 응용
// UnityEngine.Pool을 활용하여 구현
[SerializeField] private GameObject bulletPrefab;
private const int minSize = 1;
private const int maxSize = 3;
List<GameObject> tempObject;
public IObjectPool<GameObject> pool { get; private set; }
void Awake()
{
init();
}
private void init()
{
pool = new ObjectPool<GameObject>(CreateObject, GetObject, ReleaseObject,
DestroyPool, true, minSize, maxSize);
for (int i = 0; i < minSize; i++)
{
GameObject obj = CreateObject();
pool.Release(obj);
}
// maxSize 이상의 오브젝트를 관리할 임시 리스트
tempObject = new List<GameObject>();
}
// 오브젝트 생성 함수
private GameObject CreateObject()
{
GameObject newObject = Instantiate(bulletPrefab);
newObject.SetActive(false); // 초기에는 비활성화
return newObject;
}
// 사용
private void GetObject(GameObject obj)
{
obj.SetActive(true);
}
// 반환
private void ReleaseObject(GameObject obj)
{
obj.SetActive(false);
if (pool.CountInactive >= maxSize)
{
// 풀에 공간이 없으면 임시 오브젝트 리스트에 추가
tempObject.Add(obj);
}
}
// 삭제
private void DestroyPool(GameObject obj)
{
if (tempObject.Contains(obj))
tempObject.Remove(obj);
Destroy(obj);
}
}