하나의 클래스에 오직 하나의 인스턴스만 가지고 여러개의 모듈들이 인스턴스를 공유하는 패턴. 데이터베이스 연결모듈에 많이 쓰인다.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
const a = new Rectangle(1, 2) const b = new Rectangle(1, 2) console.log(a === b) // false
※ 위에서 const a와 const b는 서로 다르다. 위의 경우를 도식화하면 다음과 같다.
Rect이라는 class를 만들었다면, 이를 기반으로 a와 b라는 인스턴스로 나뉘어지게 된다.
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
싱글톤패턴의 경우 const a와 const b는 서로 같고, 이를 도식화하면 다음과 같다.
싱글톤으로 정의한 class는 하나의 인스턴스를 기반으로 a라는 객체와 b라는 객체를 만든다.
하나의 인스턴스를 기반으로 공유하는 이유는 인스턴스 생성에 대한 비용이 줄어들기 때문이다. 그렇기 때문에 "인스턴스 생성에 많은 비용"이 드는 I/O 바운드 작업에 싱글톤 패턴을 많이 사용한다.
DB와 서버 연결시, connect와 query를 보내는 과정중에 매번 connect를 하고 query를 보내게되면 connect 소모값이 커지게 된다.
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연결을 할때, DB.instance = createConnection(url)를 통해
createConnection 인스턴스를 만드는 것을 볼 수 있다.
싱글톤 패턴의 모듈 두개를 비교하면 다음과 같다.
instance가 있으면 instance를 반환하고, instance가 없다면 한번만 connect를 하는 것을 볼 수 있다.
(위는 mongoose 라이브러리 이다)
또한 mongoose 라이브러리를 보면 싱글톤 패턴이 반영되어있는 것을 볼 수 있다.
Mongoose.prototype.connect = function (uri, options, callback) { const _mongoose = this instanceof Mongoose ? this : mongoose; const conn = _mongoose.connection;
return _mongoose._promiseOrCallback(callback, cb => {
conn.openUri(uri, options, err => { if (err != null) {
return cb(err); }
return cb(null, _mongoose); });
}); };
// 메인 모듈
const mysql = require('mysql'); const pool = mysql.createPool({
connectionLimit: 10, host: 'example.org', user: 'kundol', password: 'secret', database: '승철이디비'
});
pool.connect();
// 모듈 A
pool.query(query, function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
// 모듈 B
pool.query(query, function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
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()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다.
*/
의존성이 높아지며 TDD(Test Driven Development)를 할 때 걸림돌이 된다.
TDD를 할때 단위 테스트를 주로하는데, 단위 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야한다.
하지만 싱글톤 패턴은 미리 생성된 하난의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 '독립적인' 인스턴스를 만들기가 어렵다.
(의존성)
위의 그림처럼 클래스가 변경되면 객체까지 모두 하나하나 변경되어야한다는 단점이 있다.
TDD는 순서와 상관없이 작동되어야하지만, 싱글톤 패턴은 클래스를 먼저 만들어야한다는 점에서 순서가 정해진다.
// npm install -g mocha
// mocha single1.js
const assert = require('assert');
const a = [1, 2, 3]
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal(a.indexOf(4), -1);
a[0] = 4;
});
});
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal(a.indexOf(4), -1);
});
});
});
여기에서 전역변수 a를 a[0] = 4로 수정을 해버리면 a=[4,2,3] 이 되면서 꼬여버린다.