디자인 패턴/생성 패턴

[c#] 게임으로 이해하는 [생성 패턴] 01 - 싱글톤(Singleton)

DoubleAAa 2025. 2. 9. 02:40

 

이 짤만 보고 싱글톤을 이해한다면 당신은 프로그래밍 천재!

 

아마 유니티에서 게임을 만들면 제일 먼저 접해보는 디자인 패턴이지 아닐까 싶다 ㅋㅋ

실제로도 자주 사용하는 디자인 패턴 - 생성 패턴(Creational Patterns) 중 싱글톤(SingleTon)에 대해서 알아보자

 

- 싱글톤(Singleton)은 딱 2가지만 알면 쉽다!

1. 단 하나인스턴스(Instance)생성한다!

 

2.  전역적으로 관리해야 하는 객체(Object)에서 주로 사용된다!

 

 

Q1. 그래서  싱글톤이 뭐냐니까 ???

GameManger 이다!

 

무언가를 설명할 때 예를 들어서 설명해주면 이해하기가 쉽다 (앞으로도 예시를 통해서 글을 작성할 것이다)

 

우리가 게임을 하면 캐릭터, 적캐릭터, 아이템 게임 오브젝트가 있을텐데 이들을 관리하는 시스템이 필요하기 마련이다.

게임을 개발할 때 이들을 매니저라고 부르는데, 이 때 사용하는 방법이 Singleton 이다!

 

게임의 전반적인 상황을 알려주고 관리 해주는 GameManger가 있다 치자

(현재 스테이지, 플레이어 점수, 게임 진행도... 등을 관리해주는 매니저)

 

플레이어가 메인 메뉴에서 게임 시작 버튼을 누르면, GameManger게임 상태 설정하고,

게임 화면이 변경되더라도 (이거를 씬(Scene)이 변경된다고 한다),

GameManager유지 상태로 남아 점수나 진행 상황 관리한다.

 

즉, 여러 Scene에서 GameManager가 있어야 하기 때문에 (GameMamager공유되어야 하기 때문에)

싱글톤(Singleton)을 사용하는 것!

 

 

Q2. 그러면 어떻게  싱글톤 패턴을 사용하는 건데 ???

코드를 통해서 알아보도록 하자! (맨 아래 전체 코드가 있습니다!)

먼저 GameManager 클래스부터 선언한다!

public class GameManager
{
    private static GameManager instance;

    private GameManager()
    {
        // 나는 매니저 생성자야!
    }

    public static GameManager Instance
    {
        get
        {
            if (instance == null)  // 객체가 없을 때만 생성
            {
                instance = new GameManager();
            }
            return instance;
        }
    }

    public void WriteGameMessage()
    {
        Console.WriteLine("GameManager 싱글톤 인스턴스 등장!");
    }
}

 

이 코드에선 중요한 부분이 2가지가 있다.

private static GameManager instance;	// 인스턴스
private GameManager() { }		// 생성자

 

인스턴스와, 생성자의 접근제한자를 private로 설정해 외부에서 인스턴스 생성을 방지한다!

 

 

Q3-1. 클래스는 public인데요?? 왜 인스턴스는 private인가요?

클래스가 public으로 설정되어야, 외부에서 GameManger 클래스를 참조하고 사용할 수 있다!

싱글톤의 핵심하나의 인스턴스만 생성하도록 보장되는 것이다!

 

인스턴스 private로 선언해야 instance 변수는 외부에서 직접 접근할 수 없기

객체가 외부에서 임의로 생성되거나 수정 불가능하다는 것이다!!

 

마찬가지로 생성자 private으로 하는 이유가 외부에서 객체를 생성하지 못하기 위해서 다!

단, 공유는 해야되기 때문에 클래스만 public인 것이다!!

 

 

Q3-2. public static GameManager Instance  ← 그럼 이건 뭔데요?

public static GameManager Instance { }

이 녀석은 싱글톤 패턴에서 유일한 인스턴스 생성을 관리하는 프로퍼티(Property)이다.

public static GameManager Instance
{
    get
    {
        if (instance == null)  // 객체가 없을 때만 생성
        {
            instance = new GameManager(); // 최초로 객체 생성
        }
        return instance;  // 이미 생성된 객체 반환
    }
}

 

객체가 없으면 새로운 인스턴스를 만들고, 만약 인스턴스가 있으면 자신을 반환한다.

 

이미 생선된 인스턴스만 반환해서 동일한 객체를 계속 재사용한다는 뜻이다!

 

자 그럼 이제 테스트를 해보자!

internal class Program
{
    static void Main(string[] args)
    {
        // 싱글톤 객체 호출하기
        GameManager gm1 = GameManager.Instance;
        GameManager gm2 = GameManager.Instance;

        // 생성 확인
        gm1.WriteGameMessage();
        gm2.WriteGameMessage();

        // 이 둘이 같은 객체인지 확인
        if (Object.ReferenceEquals(gm1, gm2))
        {
            Console.WriteLine($"저흰 같아요! 값:" + Object.ReferenceEquals(gm1, gm2));
        }
        else
        {
            Console.WriteLine($"저런 다른 놈이네요! 값:" + Object.ReferenceEquals(gm1, gm2));
        }
    }
}

 

gm1과 gm2라는 객체를 만들었다. 만약 저 둘이 같다면 True를 반환, 다르다면 False를 출력할 것이다.

 

 

< 출력 결과 >

오 같은 인스턴스네요!

 

 

 

 

마지막으로 이를 통해서 알 수 있는 싱글톤 패턴의 특징을 정리해보자!

 

1. 인스턴스 하나만 존재한다!

    Instance 프로퍼티를 통해서만 객체를 가져올 수 있다.

    즉, new GameManager() 단 한번만 실행되니까 모든 코드에서 같은 GameManager 인스턴스공유한다.

    따라서 데이터의 일관성이 유지된다!

 

2. 전역적 접근가능하다!

    static을 활용해 GameManager.Instance를 통해서 어디서든 쉽게 접근이 가능하다.

    단, 너무 남용하면 결합도가 높아지기에 유지보수가 좀 까다로울 수 있다 (나중에 코드가 쌓이고 쌓여지면..?)

 

 

게임 개발함에 있어 다양한 매니저가 존재한다.

게임 매니저, 캐릭터 매니저, 오디오 매니저, UI 매니저, 데이터 저장 매니저, 네트워크 매니저 등...

수많은 매니저들이 존재하는데, 이들을 다루기 쉽게 해주는 싱글톤 기법에 대해서 알아보았다.

 

문제는 다른 사이트에서도 싱글톤은 남용하면 힘들다 했는데 이건 직접 실습을 진행한 후에 왜 남용하면 안되는지

추후 이해해본 뒤에 작성해보도록 하겠습니다!

 

더보기
namespace CreatePattern_01_Singleton
{
    public class GameManager
    {
        private static GameManager instance;

        private GameManager()
        {
            // 나는 생성자야!
        }

        public static GameManager Instance
        {
            get
            {
                if (instance == null)  // 객체가 없을 때만 생성
                {
                    instance = new GameManager();
                }
                return instance;
            }
        }

        public void WriteGameMessage()
        {
            Console.WriteLine("GameManager 싱글톤 인스턴스 등장!");
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            // 싱글톤 객체 호출하기
            GameManager gm1 = GameManager.Instance;
            GameManager gm2 = GameManager.Instance;

            // 생성 확인
            gm1.WriteGameMessage();
            gm2.WriteGameMessage();

            // 이 둘이 같은 객체인지 확인
            if (Object.ReferenceEquals(gm1, gm2))
            {
                Console.WriteLine($"저흰 같아요! 값:" + Object.ReferenceEquals(gm1, gm2));
            }
            else
            {
                Console.WriteLine($"저런 다른 놈이네요! 값:" + Object.ReferenceEquals(gm1, gm2));
            }
        }
    }
}