멈추지 않고 끈질기게
[디자인 패턴] 싱글톤(Singletone) 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 하기 출처들을 참조하였습니다.
- https://gyoogle.dev/blog/design-pattern/Singleton%20Pattern.html
이번 포스팅에서는 디자인 패턴 중 하나인 싱글톤에 대해 알아보겠습니다.
1. 싱글톤(singletone)이란?
싱글톤은 디자인 패턴의 일종으로, 특정 클래스의 최초 호출 시에만 메모리를 할당하고 이후에는 생성된 인스턴스를 사용하여 해당 클래스의 인스턴스가 한개만 유지되도록 하는 방식입니다. 다음은 C#에서 싱글톤의 기본 원리만 간단히 구현한 예시입니다.
public class Singleton
{
// 생성자 private으로 선언
private Singleton() { }
// 전역 변수
private static Singleton instance;
public static Singleton Instance
{
get
{
// 최초 호출 시 생성
if (instance == null)
instance = new Singleton();
// 생성된 인스턴스 반환
return instance;
}
}
}
우선 생성자를 private로 선언함으로서 외부에서의 생성을 차단합니다. 그리고 전역변수 instance는 private으로, 외부 호출을 위한 속성 Instance는 public으로 생성합니다. 조건문으로 Instance 최초 호출 시에는 instance를 생성자를 통해 초기화하도록 해줍니다. 이제 해당 클래스는 외부에서 별도의 인스턴스를 생성할 수 없고 Instance를 통해서만 접근 가능하며, Instance는 이미 생성된 instance를 반환하므로 인스턴스가 하나로 유지됩니다.
2. 싱글톤의 장단점
싱글톤을 사용하면 해당 클래스의 인스턴스를 여러개 생성할 수 없기 때문에 메모리를 절약하는 효과가 있으며, 해당 클래스에 어디서든 접근할 수 있습니다(Instance가 전역변수이므로). 게임 제작 시 전체적인 게임 진행을 관리하는 GameManager 클래스를 만들곤 하는데, 보통 이러한 매니저 클래스를 싱글톤으로 구현합니다.
다만 싱글톤으로 구현한 클래스의 경우, OCP(Open-Closed Principle, 개방-폐쇄 원칙)를 위반하게 된다는 단점이 있습니다. OCP는 객체지향 5원칙 중 하나로, 확장에 대해서는 개방적(Open)이고 수정에 대해서는 폐쇄적(Closed)이어야 한다는 법칙입니다. 이를 지키려면 추상화 단계에서 확장될 여지가 있는 기능을 구분하고 구현 단계에서 추상화된 개념에 맞게 구현해야 합니다. 다만 객체가 너무 많은 다른 객체들과 연결될 경우 이러한 원칙을 지키며 구현하기 어려워지는 문제가 있습니다. 싱글톤으로 구현한 클래스는 다른 여러개의 객체들과 연결될 수 있으므로 OCP를 위반하게 되며, 이 경우 유지보수 난이도가 상승하는 부작용이 발생합니다.
다만 게임의 경우 전체적인 진행을 관리하기 위해 매니저 클래스가 필요하므로, 이 경우 싱글톤으로 구현하되 설계 단계에서 해당 매니저 클래스에서 관리할 기능들을 사전에 충분히 검토한 후에 진행해야 하겠습니다.
3. 싱글톤 구현 예시
다음은 제 유니티 포트폴리오 프로젝트에서 싱글톤으로 작성한 GoldManager 클래스의 코드입니다.
public class GoldManager
{
// 생성자 및 소멸자
private GoldManager()
{
gold = PlayerPrefs.HasKey(nameof(gold)) ? PlayerPrefs.GetInt(nameof(gold)) : 0;
}
~GoldManager()
{
PlayerPrefs.SetInt(nameof(gold), gold);
}
// 싱글톤 구현
private static GoldManager instance;
public static GoldManager Instance
{
get
{
if (instance == null)
instance = new GoldManager();
return instance;
}
}
int gold;
public int Gold { get { return gold; } }
//외부 사용 함수
public void PlusGold(int plus)
{
gold += plus;
PlayerPrefs.SetInt(nameof(gold), gold);
}
public bool Purchase(int price)
{
if (price > gold) return false;
gold -= price;
PlayerPrefs.SetInt(nameof(gold), gold);
return true;
}
}
생성자는 private으로 선언하고, 호출 시 PlayerPrefs에 골드값이 저장된 내역이 있다면 해당 값을 불러오고 아니면 glod 값을 0으로 초기화하도록 했습니다. 소멸자는 PlayerPrefs.SetInt()를 호출하도록 하여 현재 gold값을 저장하도록 했습니다. 전역변수 instance와 Instance는 맨 위의 예제와 동일하게 구현하여 인스턴스가 하나로 유지되도록 하였고, 골드 습득 시 호출할 PlusGold() 함수와 소모 시 호출할 Purchase() 함수를 선언하였습니다.
게임 내에서 골드를 여러개의 객체가 관리하여 서로 다른 값이 존재하면 안되므로 , 골드는 단일 객체가 관리하도록 구현해야 했습니다. 또한 골드 소모는 로비 씬의 강화 화면에서, 습득은 전투 씬에서 이루어지기 때문에 씬에 상관없이 접근할 수 있는 클래스가 필요했습니다. 따라서 골드를 관리하기 위한 GoldManager를 싱글톤으로 구현한 것입니다.