[CS] 디자인 패턴 #1 싱글톤 패턴

Youngshin Park·2025년 2월 15일

CS

목록 보기
1/5

디자인 패턴이란?

프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것

자바스크립트와 자바로 코드를 살펴보려 한다.

✅ 참고할 만한 용어

  • 라이브러리 : 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것. 폴더명, 파일명 등에 대한 규칙이 없고 프레임워크에 비해 자유로움.
  • 프레임워크 : 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것. 폴더명, 파일명 등에 대한 규칙이 있으며 라이브러리에 비해 조금 더 엄격함.

싱글톤 패턴

하나의 클래스에서 오직 하나의 인스턴스만 가지는 패턴
하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수는 있지만, 그렇게 하지 않고 1:1로만 이루어짐. 보통 데이터베이스 연결 모듈에서 많이 사용함!

이러한 패턴은 주로 프로그램 내에서 하나로 공유를 해야 하는 객체가 존재할 때 해당 객체를 싱글톤으로 구현하여 모든 유저 또는 프로그램들이 해당 객체를 공유하며 사용하도록 할 때 사용된다.

즉, 싱글톤 패턴은 아래와 같은 상황에 사용한다!

  1. 프로그램 내에서 하나의 객체만 존재해야 한다.
  2. 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용해야 한다.

장점

  • 하나의 인스턴스를 만들어놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 하나의 인스턴스를 생성할 때 드는 비용이 줄어듦

단점

  • 의존성이 높아짐

자바스크립트의 싱글톤 패턴

자바스크립트에서는 리터럴 {} 또는 new Object로 객체를 생성하게 되면 다른 어떤 객체와도 같지 않기 때문에 이 객체만으로 싱글톤 패턴 구현이 가능하다.

다음과 같이 만들 수 있다.

const obj = {
    a: 27
}
const obj2 = {
    a: 27
}
console.log(obj === obj2)
// false

설명
obj와 obj2는 다른 인스턴스를 가진다.
이 또한 어느 정도 싱글톤 패턴이라 볼 수 있는데 다음 코드가 보다 더 정확하다고 할 수 있다.

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) // true 

Singleton.instance라는 하나의 인스턴스를 가지는 Singleton 클래스를 구현한 모습이다. 이를 통해서 a와 b는 하나의 인스턴스를 가지게 된다.

두 번째 코드는 클래스 내부에 정적 프로퍼티를 사용하여 인스턴스를 한 번만 생성한다.
즉, 첫 번째 호출 시 인스턴스를 생성하고 이후 호출에서는 이미 생성된 인스턴스를 반환하기 때문에, 여러 번 생성해도 동일한 객체를 사용하게 되는 것이다.
반면에 첫 번째 코드는 객체 리터럴을 사용해 두 개의 별도 객체를 생성하므로, 메모리 상의 주소가 달라 obj === obj2가 false가 되는 것이다.


데이터베이스 연결 모듈

데이터베이스 연결 모듈에 싱글톤 패턴이 많이 쓰인다.

// DB 연결을 하는 것이기 때문에 비용이 더 높은 작업 
const URL = 'mongodb://localhost:27017/kundolapp' 
const createConnection = url => ({"url" : url})    
class DB {
    constructor(url) {
        if (!DB.instance) { 
            DB.instance = createConnection(url)
        }
        return DB.instance
    }
    connect() {
        return this.instance
    }
}
const a = new DB(URL)
const b = new DB(URL) 
console.log(a === b) // true

DB.instance라는 하나의 인스턴스를 기반으로 a,b를 생성한다. 이를 통해 인스턴스 생성 비용을 아낄 수 있는 것이다.

코드 설명을 조금만 해보겠다.
URL 상수

  • URL은 데이터베이스에 연결하기 위한 주소 저장

createConnection 함수

  • 이 함수는 주어진 URL을 받아서 연결 객체를 생성함. 예제에서는 단순히 { "url": url } 형태의 객체를 반환하지만, 실제 상황에서는 DB 연결을 수행하는 복잡한 작업이 이루어질 수 있음.

DB 클래스와 싱글톤 패턴

  • DB 클래스의 생성자에서는 먼저 if (!DB.instance) 조건문을 사용하여, 아직 인스턴스가 생성되지 않았는지 확인함.
  • 인스턴스가 없으면 createConnection(url)을 호출하여 연결 객체를 생성하고, 이를 DB.instance에 저장.
  • 이후 생성자가 호출될 때마다 이미 생성된 DB.instance를 반환하기 때문에, 여러 번 new DB(URL)을 호출해도 항상 동일한 객체를 사용하게 됨.

객체 비교

  • 마지막에 a와 b 두 변수가 new DB(URL)을 통해 생성되었지만, 내부적으로 같은 인스턴스를 참조하므로 console.log(a === b)는 true를 출력함.

결과적으로, 이 코드는 데이터베이스 연결처럼 비용이 큰 작업을 싱글톤 패턴을 통해 한 번만 수행하고, 여러 곳에서 동일한 연결 객체를 공유하도록 설계되어 있다는 점에서 효율적이다.


자바에서의 싱글톤 패턴

자바는 아래 코드와 같이 중첩 클래스를 이용해서 만드는 방법이 가장 흔하다.

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

public class HelloWorld{ 
     public static void main(String []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); 
        } 
     }
}
/*
705927765
705927765
true

1. 클래스안에 클래스(Holder), static이며 중첩된 클래스인 singleInstanceHolder를 
기반으로 객체를 선언했기 때문에 한 번만 로드되므로 싱글톤 클래스의 인스턴스는 애플리케이션 당 하나만 존재하며 
클래스가 두 번 로드되지 않기 때문에 두 스레드가 동일한 JVM에서 2개의 인스턴스를 생성할 수 없습니다. 
그렇기 때문에 동기화, 즉 synchronized를 신경쓰지 않아도 됩니다. 
2. final 키워드를 통해서 read only 즉, 다시 값이 할당되지 않도록 했습니다.
3. 중첩클래스 Holder로 만들었기 때문에 싱글톤 클래스가 로드될 때 클래스가 메모리에 로드되지 않고 
어떠한 모듈에서 getInstance()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다. 
*/

내부 정적 클래스 사용

  • Singleton 클래스 내부에 singleInstanceHolder라는 정적 클래스를 정의하고, 이 클래스 안에 private static final Singleton INSTANCE = new Singleton();를 선언함.
  • 이 방식은 초기화 지연(Lazy Initialization)스레드 안전성(Thread Safety)를 동시에 보장하는데, Singleton 클래스가 사용될 때 내부 클래스가 로딩되어 INSTANCE가 생성됨.

인스턴스 반환 메서드

  • public static Singleton getInstance() 메서드는 singleInstanceHolder.INSTANCE를 반환함.
  • 이를 통해 여러 번 호출해도 항상 같은 인스턴스를 반환하게 됨.

메인 메서드 실행

  • HelloWorld 클래스의 main 메서드에서 Singleton.getInstance()를 두 번 호출하여 a와 b에 할당함.
  • 두 변수는 동일한 싱글톤 인스턴스를 참조하게 되므로, a.hashCode()와 b.hashCode()가 동일한 값을 출력하며, a == b 비교 결과는 true가 됨.

결과적으로, 위 자바 코드는 한 번만 객체를 생성하고 이후에는 그 객체를 재사용하는 싱글톤 패턴의 장점을 잘 보여준다.


싱글톤 패턴의 단점

단점이유
전역 상태 (Global State)싱글톤은 전역 변수처럼 동작하여, 상태 변경이 애플리케이션 전반에 예기치 않은 영향을 미칠 수 있음.
테스트 어려움여러 테스트에서 동일한 인스턴스를 공유하므로, 단위 테스트 시 독립적인 환경을 만들기 어렵고 모의 객체 사용이 제한됨.
결합도 증가여러 모듈이 싱글톤 인스턴스에 의존하게 되어, 코드 간 결합도가 높아지고 유지보수 및 확장이 어려워짐.
확장성 제한인스턴스가 하나로 고정되어 있어, 여러 인스턴스가 필요한 경우 유연하게 대처하기 어려움.
상태 관리 문제mutable 상태를 가진 싱글톤은 여러 곳에서 동시에 접근할 경우, 상태 동기화 문제나 경쟁 상태가 발생할 수 있음.






** 모든 코드는 https://github.com/wnghdcjfe/csnote/tree/main/ch1 에서 참고하였습니다.

1개의 댓글

comment-user-thumbnail
2025년 2월 26일

정리가 대박이네요 !! 좋아요 누르고 갑니다 ㅎㅎ

답글 달기