저번에도 싱글톤 관련해서 글을 올렸는데 아쉽고 조금 이해하기 힘든 부분들이 있어서 새로운 글을 작성해봅니다!
디자인 패턴은 코드의 품질과 유지보수성을 높이는 데 큰 도움을 주는 도구입니다. 이번 글에서는 그중에서도 가장 자주 등장하고, 동시에 잘못 사용되기도 쉬운 싱글톤(Singleton) 패턴에 대해 깊이 있게 알아보겠습니다.
정의는 간단합니다. "인스턴스를 하나만 만들고, 그 인스턴스를 어디서든 공유하는 패턴"입니다.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
// 외부에서 생성자를 호출하지 못하게 private 처리
}
public static Singleton getInstance() {
return instance;
}
public void say() {
System.out.println("hi, there");
}
}
단순한 구조 같지만, 싱글톤은 문제의 덩어리가 될 수 있습니다.
Singleton.getInstance().setValue("A")
라는 상태 설정이 한 테스트에서 이루어졌다면, 다음 테스트에서도 같은 인스턴스를 사용하므로 이전 상태("A"
)가 그대로 유지됩니다.public class Singleton {
private static Singleton instance = new Singleton();
private String value;
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
// 테스트용 초기화 메서드 (주의: 실서비스에서는 사용 금지)
static void resetInstanceForTest() {
instance = new Singleton();
}
}
Singleton.resetInstanceForTest()
를 호출하면 초기화가 가능하지만, 이 방식은 설계적으로 좋지 않고 위험할 수 있습니다.synchronized
키워드를 사용할 수 있습니다:public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
하지만 synchronized는 성능 저하를 초래할 수 있습니다. 그 이유는 다음과 같습니다:
이를 보완하기 위해 보통 double-checked locking 패턴을 사용합니다.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
싱글톤 객체를 직접 가져다 쓰면, 클라이언트 코드가 구체 클래스에 의존하게 됩니다.
이로 인해 테스트가 어려워지고, 확장이나 변경이 어려운 구조가 됩니다.
대표적으로 SOLID 원칙 중 아래 두 가지를 위반할 수 있습니다:
DIP (Dependency Inversion Principle, 의존 역전 원칙):
OCP (Open/Closed Principle, 개방-폐쇄 원칙):
이 문제를 피하려면 인터페이스 기반 설계와 의존성 주입(DI)을 통해 유연성을 확보하는 것이 좋습니다.
절대 그렇진 않습니다. 단, 직접 구현하기보다는 프레임워크의 도움을 받는 것이 훨씬 낫다는 게 핵심입니다.
예: Spring에서는 기본적으로 Bean Scope가 Singleton이며,
컨테이너가 생명주기, 동시성, 의존성까지 안전하게 관리해줍니다.
즉, "싱글톤처럼 동작하지만, 단점은 없는 구조"를 프레임워크가 제공해주는 거죠.
싱글톤 패턴은 다음과 같은 상황에서 유용하게 사용할 수 있습니다:
설정이나 환경 정보를 전역으로 공유해야 할 때
공통 유틸리티 객체를 재사용할 때
외부 자원과의 연결을 하나로 제한해야 할 때
동일한 상태를 여러 곳에서 참조해야 할 때
다만, 아래 조건 중 하나라도 해당된다면 싱글톤 사용은 다시 한번 고려해보는 것이 좋습니다:
직접 구현한다면 동시성 처리, 상태 관리, 의존성 문제를 신중히 고려해야 하며,
가능하다면 Spring과 같은 DI 프레임워크의 지원을 받는 것이 바람직하다고 생각합니다
싱글톤 패턴은 매우 강력하지만, 신중하게 사용해야 할 도구입니다. 객체를 하나만 만든다는 단순한 규칙 뒤에는 다양한 트레이드오프가 숨어 있기 때문입니다. 잘 사용하면 효율적인 구조가 되고, 잘못 사용하면 유지보수 악몽이 될 수도 있다는 것!