싱글톤 패턴이란?
싱글톤 패턴: 특정 클래스의 인스턴스가 단 하나만 생성되는 것을 보장하는 디자인 패턴
Player 및 Monster 같은 경우 여러 개의 인스턴스가 존재할 수 있으니 싱글톤의 대상이 아니다.
GM(GameManager) 인스턴스는 씬에서 단 하나만 존재하기 때문에 싱글톤의 대상이다.
아직 감이 오지 않는다면 예를 더 들어보겠다.
어떤 게임에 Stage 클래스와 StageManager 클래스가 있다고 가정하자.
이때, Stage는 스테이지 데이터 및 행동 관리 클래스이며 StageManager는 Stage를 관리하는 클래스이다.
Stage는 게임에 여러 개로 존재할 수 있으므로 싱글톤 패턴을 적용할 수 없다.
하지만 StageManager가 10개의 Stage를 관리하는 클래스이며 단 하나만 존재하므로 싱글톤 패턴이 될 수 있다.
싱글톤 패턴의 장점
1. 접근성이 편리해진다.
싱글톤 패턴을 사용하면 유일성을 부여하는 static을 통해 인스턴스를 생성한다.
static 인스턴스이기 때문에 외부에서 호출할 때 클래스를 통해 호출할 수 있게 된다.
즉, 접근을 위해 변수(필드)로 받아오는 과정이 없어지므로 접근성이 편리해진다.
2. 유일성을 보장할 수 있다.
Manager 혹은 Controller와 같이 씬에 하나만 존재해야 하는 경우를 보장할 수 있다.
이를 통해 사전에 중복 생성을 방지하여 버그를 예방하는 용도로 쓰일 수 있다.
싱글톤 패턴의 단점
1. 의존성이 높아진다.
여러 클래스가 싱글톤을 static 참조를 하기 때문에 높은 결합을 하게 된다.
이러면 싱글톤의 필드 및 메서드가 변경되면 결합된 모든 클래스를 수정해야 하는 문제가 발생한다.
2. 테스트가 어려워진다.
단위 테스트는 독립된 상태에서 진행된다.
하지만 기능에 싱글톤 패턴이 들어가면 독립성이 깨져 테스트를 정상적으로 진행할 수 없다.
장단점을 살펴보니 싱글톤 패턴은 양날의 검이란 것을 알 수 있다.
나도 싱글톤 패턴의 편리함에 심취해 피를 본 적이 있었는데 장단점이 확 와닿는다.
싱글톤 패턴이 적용된 클래스가 커질 때마다 분할의 필요성을 검토하는 습관이 좋겠다.
싱글톤 패턴 코드 분석
public class SingletonObject
{
private SingletonObject () { }
private static SingletonObject instance;
public static SingletonObject Instance
{
get
{
if (instance == null)
{
instance = new SingletonObject();
}
return instance;
}
}
}
일반적인 싱글톤 패턴 코드
위 코드는 C#에서 사용하는 가장 기본적인 싱글톤 패턴 코드이다.
다음과 같은 특징을 가지고 있다.
☞ 외부에서 생성자 호출 불가능
☞ get만 가능 (단, null인 경우 최초 1회 생성)
이러면 싱글톤 패턴의 정의를 만족할 수 있다.
하지만 유니티에서는 위 코드로는 완벽할 수 없다.
그 이유는 다음과 같다.
MonoBehaviour 상속과 게임 오브젝트의 존재
유니티에선 Manager 혹은 Controller가 주로 싱글톤 대상이 된다.
대부분 이것들은 MonoBehaviour을 상속 받고 게임 오브젝트로서 씬에 배치된다.
이때 실수로 싱글톤이 적용된 클래스를 가진 오브젝트를 여러 개 씬에 배치한다면?
더 이상 우리가 아는 싱글톤이 아니게 되어버린다!
그래서 유니티의 싱글톤은 오브젝트 관점으로 진화하게 된다.
public class SingletonObject : MonoBehaviour
{
private static SingletonObject instance;
public static SingletonObject Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<SingletonObject>();
if (instance == null)
{
var newObj = new GameObject("SingletonObject");
instance = newObj.AddComponent<SingletonObject>();
}
}
return instance;
}
}
private void Awake()
{
var objs = FindObjectsOfType<SingletonObject>();
if (objs.Length != 1)
{
Debug.LogError("There are multiple singleton objects in the scene!");
}
DontDestroyOnLoad(gameObject);
}
}
유니티 전용 싱글톤 패턴 코드
유니티 오브젝트 관점에서 싱글톤의 정의를 만족시키다보니 코드가 길어졌다...
한번 코드 분석을 해보도록 하겠다.
❔ FindObjectOfType<SingletonObject>은 뭐야?
현재 씬에 존재하는 모든 오브젝트를 탐색하여 SingletonObject 스크립트를 가진 오브젝트를 모두 반환한다.
이를 통해 싱클톤 패턴의 개수를 파악할 수 있다.
만약 null 이라면 존재하지 않으니 새 오브젝트를 생성하고 SingletonObject 스크립트를 추가한다.
❔ Awake 함수는 왜 추가했어?
개발자 실수 혹은 코드 상의 문제로 SingletonObject 스크립트를 가진 오브젝트가 다수 생성될 수 있다.
그래서 이러한 경우를 사전에 체크하여 개발자에게 Debug 로그로 알려준다.
오브젝트를 Destroy를 할 수 있지만 성능상의 문제로 개발자에게 경고하고 직접 수정하도록 유도한다.
❔ DontDestoryOnLoad(gameobject)가 뭐야?
씬 전환 시 모든 오브젝트가 삭제되고 새롭게 생성된다.
그래서 씬 전환이 되어도 싱글톤 오브젝트가 유지되도록 한다.
싱글톤 패턴 (제너릭 형태)
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
var obj = new GameObject(typeof(T).Name);
instance = obj.AddComponent<T>();
}
}
return instance;
}
}
public virtual void Awake()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(this.gameObject);
}
else
{
Destroy(this.gameObject);
}
}
}
public class SingletonObject : Singleton<SingletonObject>
{
public override void Awake()
{
base.Awake();
// Your code here
}
}
최근에 흥미롭게 본 싱글톤 개발 방식이 있었다.
그건 바로 Generic을 활용한 모노(Mono) 싱글톤!
싱글톤을 상속받아 사용하는 방식으로 확장성이 좋은 방식이다.
큰 프로젝트를 하다가 Manager가 많아질 때마다 반복되는 싱글톤 코드가
거슬렸는데 차마 Generic으로 관리할 생각을 못했다.
모노 싱글톤의 단점은 상속관계로 인한 보안 문제이다. 부모 클래스가 파악된다면 보안상 허점이 쉽게 나타난다. 일반적으로 싱글톤은 중요한 기능을 담당하므로 이런 보안 문제를 해결하기 위한 난독화 작업을 추가하는 것이 좋다.
'유니티(Unity) > 이론 정리' 카테고리의 다른 글
[Unity Korea] 객체 지향 설계 원칙 (SOLID) (0) | 2024.11.12 |
---|---|
클래스 이름 짓기 [ 2편 ] + MVC 패턴 (2) | 2024.11.11 |
클래스 이름 짓기 [ 1편 ] (1) | 2024.11.10 |
에셋 번들(Asset Bundle) 이론 정리 (1) | 2024.03.27 |
GetComponent 성능 (1) | 2024.03.16 |