singleton 패턴은 인스턴스가 단 하나만 존재해야할 때 사용한다. 관련예는 쉽게 찾아볼 수 있다. preference를 다루는 경우 그 자체가 global 성격을 가지기 때문에 쓸 수 있다. 게임개발에선 audio manager같은경우, 오디오 입출력을 단일화 하기위해 사용한다. 모바일기기의 HW 제어같은 경우 물리적으로 하나만 존재하기 때문에, singleton사용이 이상적이다.
사용방법은 좀 다르지만 안드로이드 공용자원인 시스템 서비스들 (LocationManager, NotificationManger, ConnectivityManager, LayoutInflater 등)의 이용도 같은 맥락으로 생각할 수 있다. 각각의 프로그램이 인스턴스화 시키지 않고, 시스템에서 서비스형태로 단일 인스턴스가 동작하고 있으며 우리는 getSystemService()를 이용해 그 인스턴스의 레퍼런스를 얻어와 사용한다.
주의 : 유용해 보이지만 전역변수와 같은 냄새가 나고, 멀티 쓰레드환경에서 동기화 문제가 있을수 있으므로 남용해서는 안된다.
Singleton pattern을 다이어그램으로 도식화하면 다음과 같다.

Java
우선 이해하기 쉬운 lazy initializaton 방법의 구현은 다음과 같다.
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
}
클래스 멤버로 자기자신의 인스턴스를 가지고 있으며, 외부에서 임의로 인스턴스 생성을 막기위해 생성자가 private으로 지정되어있다. 인스턴스를 얻는 외부 인터페이스는 getInstance()인데, 필요한 경우 새로 생성하고, 이미 있다면 그 인스턴스를 돌려준다.
깔끔하고 별 문제가 없을거 같은데, 멀티 쓰레드 환경에서 getInstance()가 동시에 실행되면 문제가 발생한다고 한다. thread safe하게 만들려면 getInstance()를 synchronized로 쓰면 되겠지만, 보다 보편적인 방법은 Holder를 이용한 다음과 같다고 한다.
public class SingletonWithHolder {
private SingletonWithHolder(){}
private static class LazyHolder {
private static final SingletonWithHolder instance = new SingletonWithHolder();
}
public static SingletonWithHolder getInstance(){
return LazyHolder.instance;
}
}
JVM의 초기화 실행을 알아야 이해가 되는데, Innerclass인 LazyHolder는 SingletonWithHolder 클래스 초기화때 실행되지 않는다. getInstance()가 불려 클래스의 멤버가 호출될 때 실행되며, instance 생성이 한줄이기 때문에 쓰레드간 동기화 문제도 없다. 가장 보편적인 방법이라고 생각하면 될거같다. Initialization on demand holder idiom 위키 참조.
C#
C#에서도 자바와 상황이 비슷한데, thread-safe 의 문제가 걸려있으며, lazy initialization의 여부에 따라 갈라진다. 여기서는 둘 다 적용된 코드를 소개하며, 다른 예들에 대해선 정리가 잘된 문서를 참조. https://csharpindepth.com/articles/singleton
public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
private Singleton(){}
}
Kotlin
코틀린에서는 static이 없는대신, ‘object’라는 키워드가 있다. 여러 인스턴스를 생성할 필요가 없을 때, 굳이 클래스를 만들고 이로부터 인스턴스를 생성하는 오버헤드를 줄인 키워드로, 클래스의 형태를 가지지만 그 자체가 객체다.
눈치챘겠지만, object 키워드를 사용하면 singleton은 바로 구현된다. 어떻게 보면 언어차원에서 singleton을 지원하는걸로 볼 수도 있다. singleton에 대한 구현이 따로 필요 없으므로, 공식문서의 예제만 하나 긁어와 봤다.
object CarFactory {
val cars = mutableListOf<Car>()
fun makeCar(horsepowers: Int): Car {
val car = Car(horsepowers)
cars.add(car)
return car
}
}
‘object’의 초기화는 thread safe하다고 공식문서에서 말하고 있다. 단점이라면, 코드만 보고 singleton인지 인지하기 어렵다는정도?
Python
OOP적 설계방법론은 사실 파이썬에 그렇게 잘 맞지 않는다. 구현방법은 많지만, 보편적인 방식은 메타클래스를 이용한 방식인듯 하다. 다양한 방식에 대해선 링크 참조. https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python 메타 클래스를 이용한 구현은 다음과 같다.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=Singleton):
def __init__(self):
self.name = "Bato"
def who_are_you(self):
print(f"I'm {self.name}")
메타클래스 _instances set에 처음 생성하는 인스턴스를 저장해놓고, 다음 호출시 돌려주는 방식이다. 사용은 클래스에 metaclass로 Singleton을 지정해주면 되기 때문에 간편하다.
1 thought on “Design pattern: Singleton”