etc.

[Design Pattern] 싱글톤 패턴(Singleton Pattern)

mooni_ 2025. 4. 5. 15:58

싱글톤 패턴(Singleton Pattern) : 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

> 인스턴스(Instance) : 클래스를 기반으로 실제로 만들어진 객체

 

 

EX) JavaScript에서의 Singleton

class Singleton {
    constructor() {
        if(!Singleton.instance) {
            Singleton.instance = this
        }
        return Singleton.instance
    }
    getInstance() {
    	return this
    }
}

const a = new Singleton()
const b = new Singleton()
console.log(a === b) //?

이때 a === b는 무슨 결과를 나타낼까?

.

.

.

정답은 true이다. 아래 내용을 따라 JS에서의 객체 생성 과정과 결과 값이 true인 이유를 찾아보자.

 

1. new 키워드와 클래스 생성자 동작

const a = new Singleton()
  • 새로운 빈 객체({})가 생성됨
  • 이 빈 객체의 prototype을 Singleton.prototype으로 설정
  • 이 객체를 this로 하여 constructor가 실행됨
  • constructor가 명시적으로 객체를 반환하면, 그 객체가 new Singleton()의 결과가 됨
  • 아니면, this (기본적으로 생성된 객체)가 반환됨

 

2. 생성자에서 Singleton 인스턴스를 명시적으로 반환

if(!Singleton.instance) {
    Singleton.instance = this
}
return Singleon.instance

=> return Singleton.instance를 통해 명시적 반환을 함으로서 해당 객체가 instance로 사용됨.

 

3. Singleton 패턴 적용 : 인스턴스는 단 하나만

const a = new Singleton() // Singleton.instance가 없으므로 새로 생성 -> 저장
const b = new Singleton() // Singleton.instance가 있으므로 그걸 반환

결과적으로 a와 b는 모두 Singleton.instance를 참조하게 됨

 

4. 동일한 객체를 참조하기 때문에 a === b : true

 

 

EX) Java에서의 Singleton

class Singleton {
    private static class singleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singletone getInstance() {
        return SingleInstanceHolder.INSTANCE;
    }
}

public class HelloWorld {
    public static void main(Stinrg[] args) {
        Singleton a = Singleton.getInstance();
        Singleton b = SIngleton.getInstance();
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        if(a === b) {
            System.out.println(true);
        }
    }
}

JAVA 코드에서의 출력값도 고민해보자.

싱글톤으로 생성된 객체이기 때문에 a와 b의 hashcode는 동일하고 true는 출력이 되었을 것이다.

 

1. 내부 정적 클래스 방식의 Singleton

=> 이 코드는 Java의 Lazy Initialization + Thread-safe Singleton을 구현하는 대표적인 방식이다.

=> 내부 static 클래스는 class가 처음 참조될 때만 로딩이 되므로 성능이 좋고 thread-safe 하다는 장점이 있음

 

2. 호출 흐름

Singleton.getInstance()
  • 위 코드 실행 시 SingleInstanceHolder 클래스가 로딩되며 그 안의 INSTANCE 정적 필드가 초기화되고 반환됨
  • 이후의 getInstance() 호출은 같은 ISNTANCE를 반환함.

 


싱글톤 패턴의 단점?

1. 테스트(Test Driven Developmenr)가 어려움

  • 싱글톤 객체는 전역 상태를 가지므로 여러 테스트 케이스에서 상태가 공유됨
  • 테스트 간의 의존성이 생기고 테스트 결과가 순서에 따라 달라질 수 있는 문제 발생

2. 모듈간의 결합도가 강해짐

  • 모듈 간의 독립성을 해치고 테스트나 확장에 어려움을 줌
  • 이때 의존성 주입(Dependency Injection)을 통해 해소할 수 있음
  • 직접 호출하지 않고 외부에서 주입받도록 설계

+) 의존성 주입(DI) 원칙 : 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다. 또한 둘 다 추상화에 의존해야하며, 이때 추상화는 세부 사항에 의존하지 말아야 한다.