디자인패턴

[디자인패턴] 싱글톤 패턴 (Singleton Pattern)

달시_Dalsi 2024. 12. 16. 05:35

싱글톤 패턴의 개념

싱글톤(Singleton)은 오직 하나의 인스턴스만 존재하도록하고 어디에서든 이를 접근할 수 있도록 하는 디자인 패턴입니다.
유니티에서 특히 게임 매니저, 오디오 매니저, 데이터 관리 클래스 등을 구현할 때 많이 사용됩니다.

 

싱글톤 패턴의 핵심 특징

1. 전역 접근 가능성: 싱글톤은 전역적으로 접근 가능하며, 어느 스크립트에서든 동일한 인스턴스에 접근할 수 있습니다. 2. 인스턴스의 유일성: 싱글톤은 하나의 클래스에 오직 하나의 인스턴스만 존재하도록 해야합니다.

3. 씬 간 데이터 유지: DontDestroyOnLoad를 활용해 싱글톤 오브젝트를 특정 씬에서 파괴되지 않도록 하여 이를 통해 게임 매니저, 플레이어 정보, 설정 데이터 등이 씬 전환 시에도 유지됩니다.

4. 간단한 상태 관리: 전역 설정 데이터(음량, 그래픽 옵션 등), 게임 진행상태 등 싱글톤은 게임의 전역 상태관리에 유용합니다.

5. 유니티내에 싱글톤 사용 예시:

  • GameManager: 게임 상태와 전역 데이터를 관리.
  • AudioManager: 배경음악, 효과음을 통합 제어.
  • InputManager: 사용자 입력을 전역적으로 처리.
  • NetworkManager: 네트워크 연결 및 데이터 전송 관리.
  • ResourceManager: 리소스 로드 및 캐싱.

 

싱글톤 사용 시 단점

  1. 유일한 인스턴스
    • 싱글톤은 인스턴스를 하나로 유지해야 하지만, 코드 작성등에서 의도치 않게 여러 인스턴스가 생성될 가능성이 있습니다. 이를 방지하려면 구현 및 검증에 세심한 주의가 필요합니다.
  2. 멀티스레드 환경에서의 주의
    • 멀티스레드 환경에서 싱글톤 인스턴스를 초기화할 때 중복이 발생할 수 있습니다. 이를 방지하려면 스레드 안전 처리를 해야 하며, lock 문 같은 추가적인 코딩이 필요합니다.
  3. 메모리 관리 문제
    • 싱글톤 객체가 전역에서 유지되므로, 파괴하지 않는 한 메모리에서 해제되지 않습니다. 특히, 게임에서 씬 전환이 잦은 경우 불필요한 리소스가 계속 유지되어 메모리 누수 문제가 발생할 수 있습니다.
  4. 의존성 증가
    • 싱글톤은 전역 상태를 유지하기 때문에, 다양한 클래스가 싱글톤에 의존하게 됩니다. 이는 객체 간 결합도가 높아져 유지보수성 및 확장성이 어려워지는 원인이 됩니다.

 

싱글톤 패턴 코드 구현

기본적인 싱글톤 코드 (씬에 AudioManager가 반드시 존재한다고 가정)

using UnityEngine;

public class SingletonExample : MonoBehaviour
{
    public static SingletonExample Instance;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject); // 씬 전환 시에도 파괴되지 않음
        }
        else
        {
            Destroy(gameObject); // 중복 방지
        }
    }
    
    // 다른 스크립트에서 AudioManager 사용 예제
	// SingletonExample.Instance.PlaySound("BackgroundMusic");
	public void PlaySound(string soundName)
	{
   	 	Debug.Log("playing sound : " + soundName);
	}
}

 

코드 설명

  1. 싱글톤 패턴
    • Instance라는 정적 변수를 선언하여, 클래스의 유일한 인스턴스를 제공합니다.
    • Awake() 메서드에서 Instance가 null인 경우에만 this를 할당하여 해당 인스턴스를 초기화합니다.
    • DontDestroyOnLoad(gameObject)로 씬 전환 시에도 해당 오브젝트를 파괴하지 않도록 설정합니다.
    • Instance가 이미 존재하면, 중복 인스턴스를 방지하기 위해 Destroy(gameObject)를 호출하여 객체를 삭제합니다.
    • 씬의 오브젝트에 스크립트를 반드시 넣어 사용해야합니다.
  2. 다른 스크립트에서의 사용
    • SingletonExample.Instance.PlaySound("BackgroundMusic")처럼 다른 스크립트에서 싱글톤 인스턴스를 통해 메서드를 호출할 수 있습니다. 이는 게임 전체에서 AudioManager 또는 다른 중요 관리자의 인스턴스를 쉽게 접근할 수 있게 해줍니다.

 

멀티쓰레드 환경 싱글톤 코드
(씬에 AudioManager가 없을 수도 있거나, 멀티 씬 환경에서 싱글톤을 관리하려는 경우)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AudioManager : MonoBehaviour
{
    private static AudioManager _instance;
    private static readonly object _lock = new object();

    // 아래의 2개의 Instance 프로퍼티 중 설명을 보고 자신의 환경에 맞게 사용
    // AudioManager가 씬에 없을 수도 있는 경우 or 멀티씬 환경(DontDestroyOnLoad를 사용해 AudioManager가 여러 씬에서 중복)
    public static AudioManager Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    // 새로운 AudioManager GameObject 생성
                    GameObject singletonObj = new GameObject("AudioManager");
                    _instance = singletonObj.AddComponent<AudioManager>();
                    DontDestroyOnLoad(singletonObj);
                }
                return _instance;
            }
        }
    }
    // 씬에 AudioManager가 존재하는 경우 or 인스턴스를 강제로 배치한 경우
    public static AudioManager Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    Debug.LogError("AudioManager is not in the scene!");
                }
                return _instance;
            }
        }
    }


    private void Awake()
    {
        lock (_lock)
        {
            // 다른 인스턴스가 이미 존재하면 파괴
            if (_instance == null)
            {
                _instance = this;
                DontDestroyOnLoad(gameObject); // 씬 전환 시에도 유지
            }
            else if (_instance != this)
            {
                Destroy(gameObject); // 중복 인스턴스 방지
            }
        }
    }


    // 다른 스크립트에서 AudioManager 사용 예제
    // AudioManager.Instance.PlaySound("BackgroundMusic");
    public void PlaySound(string soundName)
    {
        Debug.Log("playing sound : " + soundName);
    }
}

 

코드 설명

  1. 싱글톤 인스턴스
    • Instance 프로퍼티는 두 가지 방식으로 작성되어 있습니다. 첫 번째는 씬에 AudioManager가 없는 경우에 동적으로 새로운 GameObject를 생성하고, 두 번째는 씬에 이미 AudioManager가 존재하는 경우에 기존 인스턴스를 반환합니다.
  2. 멀티쓰레드 안전성
    • lock (_lock)을 사용하여 멀티쓰레드 환경에서의 안전성을 보장합니다. 여러 스레드가 동시에 Instance에 접근할 경우를 대비해 lock을 사용하여 동시에 하나의 스레드만 인스턴스에 접근할 수 있도록 제한합니다.
  3. 중복 인스턴스 방지
    • Awake()에서 Instance가 이미 존재하는지 체크하고, 이미 존재하면 중복 인스턴스를 파괴합니다. 이로 인해 한 게임 세션에서 AudioManager는 한 번만 존재하게 됩니다.

 

유니티에서 싱글톤 사용 예시 

싱글톤은 주로 게임 매니저(GameManager), 오디오 매니저(AudioManager), 네트워크 매니저(NetworkManager)와 같은 전역적으로 접근 가능한 시스템을 관리하는 데 적합합니다.

AudioManager 스크립트(싱글톤)

 

AudioManager를 사용할 스크립트

public class PlayObject : MonoBehaviour
{
    void Start()
    {
        AudioManager.Instance.PlaySound("BackgroundMusic");
    }
}

 

 

게임 시작시 PlayObject의 스크립트가 작동하면 아래 화면처럼 AudioManager의 PlaySound함수를 가져와 사용합니다.

 

마무리

싱글톤 패턴은 중앙 집중식 관리, 전역 상태 유지, 씬 간 데이터 유지 등의 장점 덕분에 게임 개발에서 매우 유용합니다. 하지만 중복 인스턴스, 메모리 누수, 코드 의존성 등의 단점도 존재하므로, 이를 해결하기 위한 신중한 구현과 관리가 필요합니다. 싱글톤을 사용할 때는 필요한 곳에서만 사용하고, 테스트와 유지보수에 문제가 생기지 않도록 주의해야 합니다. 

 

싱글톤 사용 주의 사항:

  • 명확한 사용 목적: 싱글톤을 언제 사용해야 하는지에 대해 명확한 기준을 설정합니다.
    (ex: GameManager, AudioManager 등)
  • 적절한 메모리 관리: 씬 전환 시 불필요한 리소스가 유지되지 않도록 메모리 관리를 신경 씁니다.
  • 멀티스레드 환경 고려: 멀티스레드 환경에서 싱글톤을 사용할 경우, 동시 접근을 방지할 수 있는 코드를 작성합니다.
  • 코드 의존성: 싱글톤 패턴은 전역 상태를 공유하므로 의존성이 증가할 수 있고, 과도한 의존성은 유지보수성과 확장성에 어려움을 줄 수 있습니다.