제네릭 싱글톤

woollim·2024년 10월 25일
1

Unity개념

목록 보기
4/8


■ 제네릭 싱글톤 (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);
    }
}

0개의 댓글