프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것
자바스크립트와 자바로 코드를 살펴보려 한다.
✅ 참고할 만한 용어
- 라이브러리 : 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것. 폴더명, 파일명 등에 대한 규칙이 없고 프레임워크에 비해 자유로움.
- 프레임워크 : 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것. 폴더명, 파일명 등에 대한 규칙이 있으며 라이브러리에 비해 조금 더 엄격함.
하나의 클래스에서 오직 하나의 인스턴스만 가지는 패턴
하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수는 있지만, 그렇게 하지 않고 1:1로만 이루어짐. 보통 데이터베이스 연결 모듈에서 많이 사용함!
이러한 패턴은 주로 프로그램 내에서 하나로 공유를 해야 하는 객체가 존재할 때 해당 객체를 싱글톤으로 구현하여 모든 유저 또는 프로그램들이 해당 객체를 공유하며 사용하도록 할 때 사용된다.
즉, 싱글톤 패턴은 아래와 같은 상황에 사용한다!
- 프로그램 내에서 하나의 객체만 존재해야 한다.
- 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용해야 한다.
장점
단점
자바스크립트에서는 리터럴 {} 또는 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 상수
createConnection 함수
DB 클래스와 싱글톤 패턴
객체 비교
결과적으로, 이 코드는 데이터베이스 연결처럼 비용이 큰 작업을 싱글톤 패턴을 통해 한 번만 수행하고, 여러 곳에서 동일한 연결 객체를 공유하도록 설계되어 있다는 점에서 효율적이다.
자바는 아래 코드와 같이 중첩 클래스를 이용해서 만드는 방법이 가장 흔하다.
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()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다.
*/
내부 정적 클래스 사용
인스턴스 반환 메서드
메인 메서드 실행
결과적으로, 위 자바 코드는 한 번만 객체를 생성하고 이후에는 그 객체를 재사용하는 싱글톤 패턴의 장점을 잘 보여준다.
| 단점 | 이유 |
|---|---|
| 전역 상태 (Global State) | 싱글톤은 전역 변수처럼 동작하여, 상태 변경이 애플리케이션 전반에 예기치 않은 영향을 미칠 수 있음. |
| 테스트 어려움 | 여러 테스트에서 동일한 인스턴스를 공유하므로, 단위 테스트 시 독립적인 환경을 만들기 어렵고 모의 객체 사용이 제한됨. |
| 결합도 증가 | 여러 모듈이 싱글톤 인스턴스에 의존하게 되어, 코드 간 결합도가 높아지고 유지보수 및 확장이 어려워짐. |
| 확장성 제한 | 인스턴스가 하나로 고정되어 있어, 여러 인스턴스가 필요한 경우 유연하게 대처하기 어려움. |
| 상태 관리 문제 | mutable 상태를 가진 싱글톤은 여러 곳에서 동시에 접근할 경우, 상태 동기화 문제나 경쟁 상태가 발생할 수 있음. |
** 모든 코드는 https://github.com/wnghdcjfe/csnote/tree/main/ch1 에서 참고하였습니다.
정리가 대박이네요 !! 좋아요 누르고 갑니다 ㅎㅎ