반응형

Singleton Pattern 이란?

싱글톤 패턴 Singleton Pattern은 클래스의 객체를 하나만 생성하도록 하는 패턴입니다. 단 하나만 유일하게 생성되기 때문에 어디에서든 그 객체를 참조할 수 있도록 하는 패턴입니다. 전역 변수를 사용하지 않고 어디서든 접근할 수 있는 객체라는 점이 중요합니다. 유니티에서는 Controller나 Manager라는 이름이 붙은 클래스 스크립트로 이 패턴을 자주 사용하곤 합니다. 

 

게임을 데이터를 저장한다던가 사용자의 클릭을 감지하는 등 게임 전반에 전체에 사용되는 로직들이 싱글톤 패턴의 예시가 될 것입니다. 유니티의 DontDestroyOnLoad()에 지정된 Managers라는 싱글톤 클래스가 있다면 게임이 종료되지 않는 이상(씬 전환에도 유지) 스크립트 어디서든 Managers.SingletonInstance.SaveData()와 같은 형태로 게임을 저장할 수 있을 것입니다.

Singleton Pattern의 장단점

싱글톤 패턴이 좋다 그렇지 않다는 유니티에서도 그렇고 빠지지 않는 논쟁거리입니다. 사실 답은 명확합니다. 적절한 상황에 쓰인다면 싱글톤 패턴이 좋을 것이고, 부적절하게 쓰인다면 싱글톤 패턴이 나쁘게 됩니다.

 

사실 유니티에서 싱글톤이 이 이슈가 자주 등장하는 가장 큰 이유 중 하나는 static 문법과 싱글톤 클래스를 남발하면 코드 작성이 그 순간 편리하게 느껴지기 때문입니다. 프로그래밍을 언어를 처음 배울 때 전역 변수를 사용하지 말아야 하는 이유에 대해서 배우곤 합니다. 그와 비슷하게 남발되는 싱글톤 클래스나 static은 추후 문제가 일어날 가능성이 높기 때문에 싱글톤 패턴이 기피되는 패턴으로 여겨집니다.

 

유니티에서는 Monobehaviour를 상속받는 클래스가 많습니다. Monobehaviour를 상속받는 클래스가 싱글톤으로 활용될 경우 조금 복잡한 상황이 연출되는 것도 싱글톤을 기피하려는 이유 중 하나일 것입니다. Monobehaviour를 상속받을 경우 생성자 constructor를 사용할 수 없기 때문에 같은 기능을 하는 코드를 직접 작성해야 합니다. 그리고 게임 내에서 Monobehavior를 상속받는 싱글톤(을 의도한) 클래스의 인스턴스를 실수로 여럿을 생성할 수도 있습니다.

 

그럼에도 불구하고 싱글톤은 유일하게 생성되는 객체를 어디에서든 접근할 수 있는 강력함 때문에 적절한 상황에서는 사용하는 것이 좋습니다. 사실 싱글톤이라는 이름부터 그러하듯 "지금 내가 구현하려는 것에 싱글톤 패턴을 사용하는 것이 좋은가?" 라는 질문에 가장 크게 고려할 점은 "정말 이 클래스의 인스턴스가 반드시 하나만 생성되는가?"입니다. 추후 기능이 추가되거나 변경되면서 싱글톤이라고 생각했던 클래스가 더 이상 싱글톤이 아니게 된다면 코드 유지보수는 매우 어려워질 것입니다. 혹은 우회하는 다른 방법을 찾아야 될 수도 있겠네요.

Singleton Pattern의 대안

싱글톤 패턴의 대안이 될 수 있는 몇 가지 방법들을 알아두는 것도 싱글톤을 상요할 수 있는 적절한 상황인지를 판단하는 데 도움이 될 것입니다.

  • 범용성이 적어 특정한 클래스에만 쓰이는 기능(코드)이라면 굳이 싱글톤으로 구현하지 않고 그 클래에 그 코드를 넣는 것으로 해결할 수도 있습니다.
  • 유니티에서 제공되는 Debug.Log나 Math.Random와 같이 static class로 해결하는 것이 더 좋을 수도 있습니다.
  • 유니티에서 Find()와 같은 빌트인 메소드는 cost가 비싸기 때문에 기피하는 것이 좋지만, Start 같은 곳에서 한번 호출되는 경우라면 큰 상관이 없다는 것을 유념해야 합니다.
  • 의존성 주입(DI : Dependency Injection)으로 해결가능한 문제일 수도 있습니다.

유니티에서 Singleton Pattern 예시와 활용법

유니티에서는 기본적으로 싱글 스레드 기반으로 동작합니다. 물론 상황에 따라 개발자가 복잡한 길 찾기, 비동기 환경 등을 게임에서 구현해야 할 때 성능 향상이 필요하다면 멀티스레딩을 명시하여 직접 구현할 수도 있습니다. 이 경우에는 싱글톤 클래스를 작성할 때 멀티스레딩 환경을 고려해야 합니다. 여기서는 일반적으로 C#에서 thread-safe를 고려한 싱글톤 구현 방식이 아니라 싱글스레드 환경에서 예시를 들어보겠습니다.

 

아래 코드는 게임 전반에서 사용될 Managers 싱글톤 클래스입니다. Managers는 여러 종류의 세분화된 싱글톤 매니저 클래스를 포함하는 하나의 큰 싱글톤 클래스입니다.

public class Managers : MonoBehaviour
{
    static Managers s_instance;
    public static Managers Instance { get { return s_instance; } }

    // 각각의 매니저들도 싱글톤입니다.
    private static DataManager _data = new DataManager();
    private static ResourceManager _resource = new ResourceManager();
    // more managers can be added here

    public static DataManager Data { get { return _data; } }
    public static ResourceManager Resource { get { return _resource; } }

    void Start()
    {
        if (s_instance == null)
        {
            GameObject go = GameObject.Find("Managers");
            if (go == null)
            {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }

            DontDestroyOnLoad(go);
            s_instance = go.GetOrAddComponent<Managers>();
        }
    }
}

이제 위 Managers를 다른 곳에서 사용해보겠습니다.

public class Enemey{
    int health;
    
    void Start()
    {
    	InitEnemyInfo();
    }

	void InitEnemyInfo(int enemyId) {
    	health = Managers.Data.EnemyData[enemyId].health;
    }
}

 

 

 

 

참고자료

https://github.com/Habrador/Unity-Programming-Patterns

 

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기