본 글은 유니티에서 발간한 전자책 'Level up your programming with game programming patterns'을 정리한 글입니다. 원문은 다음 링크에서 확인하실 수 있습니다.
https://unity.com/kr/resources/level-up-your-code-with-game-programming-patterns
1. Singleton pattern
싱글톤 패턴은 다음과 같은 기능을 한다.
- 클래스가 객체를 하나만 가지도록 보장한다.
- 그 단일 객체에 대한 쉬운 전역 액세스(global access)를 할 수 있도록 한다.
싱글톤 패턴은 모든 씬에서 작업을 조정해야 하는 오브젝트가 딱 한 개 필요한 경우에 유용하다. 가령 씬에서 메인 게임 루프를 지시하는 하나의 게임 매니저가 필요할 수 있고, 파일 시스템에 쓰기를 수행하는 하나의 파일 매니저가 필요할 수 있을 것이다. 이러한 중앙 관리자 수준의 오브젝트들은 보통 싱글톤 패턴에 적합한 후보이다.
하지만 싱글톤 패턴은 그 평판이 좋지 않다. 도서 Game Programming Patterns에서는 싱글톤 패턴의 사용이 득보다 실이 크다고 하며 싱글톤 패턴을 안티 패턴(anti-pattern)에 포함시킨다. 이러한 나쁜 평판은 싱글톤 패턴의 쉬운 사용으로 자칫 패턴이 남용될 수 있기 때문인데, 개발자들은 종종 싱글톤을 부적절한 상황에 적용하여 불필요한 전역 상태(global state)나 의존성(dependency)을 도입하곤 한다.
2. Example: Simple singleton
가장 간단한 싱글톤은 다음과 같이 생겼을 것이다.

public static의 Instance는 씬 속 단 하나의 Singleton 객체를 가리킨다. public static 필드로 인해 씬의 모든 컴포넌트가 싱글톤에 접근할 수 있다.
Awake에서는 Instance가 존재하는 지를 체크하는데, Instance가 null이면 Instance는 해당 클래스의 객체를 가리킨다. Instance가 null이 아니면 이미 해당 클래스의 객체가 씬에 존재한다는 것이므로, 게임 오브젝트를 파괴하여 싱글톤이 씬에서 단 하나의 컴포넌트만을 가지도록 한다.
3. Persistence and lazy instantiation
위 예시의 SimpleSingleton은 잘 작동하지만 다음과 같은 문제들이 있다.
- 새 씬을 로드하면 게임 오브젝트가 파괴된다.
- 싱글톤을 사용하기 전에도 하이라키에 싱글톤이 준비되어 있어야 한다.
싱글톤은 종종 모든 곳에서 사용되는 매니저 스크립트로 작용하기 때문에, DontDestroyOnLoad를 사용하여 싱글톤을 지속적으로 유지(persistent)하게 하는 것이 유용할 수 있다.
더 나아가 lazy instantiation을 사용하여 싱글톤이 실제로 처음 필요할 때 자동적으로 셋업할 수 있다. 이를 위해선 빈 게임 오브젝트를 생성하고 적절한 싱글톤 컴포넌트를 붙이는 로직만 있으면 된다.
이를 바탕으로 개선된 싱글톤은 다음과 같다.

Instance는 private 필드의 instance를 참조하는 public 프로퍼티이다. 처음 싱글톤을 참조하게 되면 instance의 존재 여부를 체크하고, instance가 존재하지 않으면 SetupInstance()를 통해 싱글톤 컴포넌트가 붙여진 게임 오브젝트를 생성한다.
DontDestroyOnLoad(gameObject)는 씬이 로드될 때 하이라키에서 싱글톤이 파괴되는 것을 방지한다. 이제 싱글톤 객체는 게임에서 씬이 바뀌더라도 유지된다.
4. Using generics
위 스크립트들은 같은 씬 안에 서로 다른 싱글톤들을 생성하는 것에 대해 다루지 않는다.
가령 오디오 매니저로 작동하는 싱글톤과 게임 매니저로 작동하는 다른 싱글톤이 필요한 경우, 위 코드로서는 두 싱글톤이 함께 존재할 수 없다. 여러 싱글톤이 존재하게 하려면 싱글톤으로 만드려는 각 클래스에 관련 코드를 복제해야 할 것이다.
대신에 다음과 같은 보다 일반적인(generic) 버전의 스크립트를 만들 수 있다.


이렇게 함으로써 어떤 클래스도 싱글톤으로 만들 수 있다. 그저 클래스를 선언할 때 위의 generic 싱글톤을 상속시키면 된다. 가령 GameManager라는 Monobehaviour를 다음과 같이 싱글톤으로 만들 수 있을 것이다.

이제 언제든 필요할 때 public static의 GameManager.Instance를 참조할 수 있다.
5. Pros and cons
이 가이드의 다른 패턴들과 달리 싱글톤은 여러 측면에서 SOLID 원칙을 위반하며, 많은 개발자들은 다음과 같은 다양한 이유로 싱글톤을 싫어한다.
- 싱글톤은 전역 액세스를 요구한다.
싱글톤은 전역 인스턴스로 사용되기 때문에 많은 의존성을 숨길 수 있고, 따라서 버그를 해결하기가 훨씬 어려워진다.
- 싱글톤은 테스트를 어렵게 한다.
유닛 테스트들은 서로 독립적이어야 한다. 하지만 싱글톤은 많은 게임 오브젝트들의 상태를 바꿀 수 있고, 이것이 테스트를 방해할 수 있다.
- 싱글톤은 높은 결합도(coupling)를 유발한다.
이 가이드의 패턴 대부분이 의존성을 줄이려고 하지만, 싱글톤은 그 반대다. 높은 결합도로 인해 한 컴포넌트를 변경하는 것이 연결된 다른 컴포넌트에 영향을 줄 수 있으며, 이로 인해 리팩토링이 어려워진다.
싱글톤에 대한 부정적인 의견은 상당하다. 앞으로 여러 해간 유지보수할 것으로 예상되는 기업급 게임을 개발 중이라면, 싱글톤을 피하는 것이 좋을 수 있다.
그러나 많은 게임이 기업급 애플리케이션은 아니며, 비즈니스 소프트웨어와 같이 계속 확장할 필요는 없다. 확장성이 필요하지 않은 작은 게임을 개발 중이라면 싱글톤이 제공하는 몇 가지 이점이 매력적일 수 있다:
- 싱글톤은 비교적 빨리 배울 수 있다.
패턴 자체가 다소 직관적이다.
- 싱글톤은 사용하기 쉽다.
다른 컴포넌트에서 싱글톤을 사용하려면 public static 인스턴스를 참조하기만 하면 된다. 싱글톤 인스턴스는 씬 내의 모든 컴포넌트에서 언제든 요청하여 사용할 수 있다.
- 싱글톤은 성능이 우수하다.
static 싱글톤 인스턴스에 항상 전역 액세스할 수 있기 때문에 GetComponent 또는 Find 작업의 결과를 캐싱하는 느린 작업을 피할 수 있다.
싱글톤을 사용하여 게임 플로우 매니저나 오디오 매니저와 같은 다른 게임 오브젝트에서 항상 접근 가능한 매니저 오브젝트를 만들 수 있다. 이를 위해 프로젝트에 싱글톤을 사용하기로 결정했다면 그것을 최소한으로 유지해라. 무분별하게 사용하지 않고, 전역 액세스에서 이점을 얻을 수 있는 소수의 스크립트에만 싱글톤을 보존해야 한다.
'Unity > Study' 카테고리의 다른 글
| Unity C#) 디자인 패턴 가이드 #6. 스테이트 패턴(State pattern) (0) | 2024.03.17 |
|---|---|
| Unity C#) 디자인 패턴 가이드 #5. 커맨드 패턴(Command pattern) (0) | 2024.03.11 |
| Unity C#) 디자인 패턴 가이드 #3. 오브젝트 풀(Object pool) (1) | 2024.03.01 |
| Unity C#) 디자인 패턴 가이드 #2. 팩토리 패턴(Factory pattern) (0) | 2024.02.21 |
| Unity C#) 디자인 패턴 가이드 #1. SOLID 원칙 (2) | 2024.02.14 |