Singleton pattern

김상민·2023년 4월 12일
0

디자인패턴

목록 보기
2/3

image_reference

📌 Singleton pattern이란?

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다.

  • 만들어진 Singleton 인스턴스는 앱 전역에서 공유되기 때문에 앱의 전역 상태를 관리하기에 적합하다.
  • 하나의 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스 생성 비용이 줄어든다.
  • 주로 데이터베이스 연결 모듈에 많이 사용한다.

💡 Singleton pattern 예시 1

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

a와 b는 하나의 인스턴스를 가진다.

💡 Singleton pattern 예시 2

인스턴스가 존재할 경우 에러를 던져주는 것으로 인스턴스를 하나만 생성할 수 있게한다.

let counter = 0;

class Counter {
    constructor() {
        if (Counter.instance) {
            throw new Error("You can only create one instance!");
        }
        Counter.instance = this;
    }

    getInstance() {
        return this;
    }

    getCount() {
        return counter;
    }

    increment() {
        return ++counter;
    }

    decrement() {
        return --counter;
    }
}

const counter1 = new Counter();
const counter2 = new Counter();
// Error: You can only create one instance!

💡 Singleton instance export 하기

Counter 인스턴스를 export 하기 전에 인스턴스를 freeze 하도록 한다.

class Counter {
   ...
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter

Object.freeze 메서드는 객체를 사용하는 쪽에서 직접 객체를 수정할 수 없도록 해준다.

freeze 처리 된 인스턴스는 프로퍼티의 추가 및 수정이 불가하므로 Singleton 인스턴스의 프로퍼티를 덮어쓰는 실수를 예방할 수 있다.

💡  Singleton pattern은 객체 리터럴로도 구현할 수 있기 때문에 위의 예제는 오버 엔지니어링이라고 볼 수도 있다.

💡 객체 리터럴 사용하기

let count = 0;

const counter = {
  increment() {
    return ++count;
  },
  decrement() {
    return --count;
  }
};

Object.freeze(counter);
export { counter };

📌 데이터베이스 연결 모듈

Singleton pattern은 데이터베이스 연결 모듈에 많이 쓰인다.

💡 mongodb 연결 모듈

const URL = "mongodb://localhost:27017/kundolapp";
const createConnection = (url) => ({ url: url });
class DB {
    constructor(url) {
        if (!DB.instance) {
            DB.instance = this;
        }
        return DB.instance;
    }
    connect() {
        return this.instance;
    }
}
const DB1 = new DB(URL);
const DB2 = new DB(URL);
console.log(a === b);

DB.instance라는 하나의 인스턴스를 기반으로 a, b를 생성한다.

💡 mongoose의 Singleton pattern

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);
        });
    });
};

📌 Singleton pattern의 단점

📍 Testing

  • Singleton pattern에서는 하나의 인스턴스를 기반으로 구현하기 때문에 단위 테스트에서 독립적인 인스턴스를 만들기가 어렵다.
  • 모든 테스트들이 전역 인스턴스를 수정하도록 테스트를 한다면, 작은 수정 사항이 전체 테스트의 실패로 이어질 수 있다.

📍Dependency hiding

다른 모듈로부터 import될 때 Singleton인지 아닌지 분명하지 않기 때문에 여러 Singleton 인스턴스들이 앱에서 공유될 때 직접 수정하게 될 수 있고 예외로 이어질 수 있다.

📍Global behavior

  • Singleton 인스턴스는 앱의 전체에서 참조할 수 있어야 한다.
    만약 전역 변수가 잘못된 판단으로 올바르지 않게 만들어진 경우, 잘못된 값으로 덮어쓰여질 수 있으며, 이 변수를 참조하는 구현들이 모두 예외를 발생시킬 수 있다.
  • 전역 변수를 만드는 것은 일반적으로 잘못된 설계라고 여긴다.
    이에 따라 ES6에서는 전역 변수를 생성하지 않고 letconst 키워드들은 변수를 블록 스코프 내에 선언하게 하여, 실수로 전역에 변수를 선언하는것을 예방한다.
    또, 새로운 module 시스템은 export , import 구문으로 전역 객체를 수정하지 않고 모듈 내에서 전역으로 쓸 수 있는 변수를 만들게 해 준다.

그러나 Singleton pattern은 일반적으로 앱에 전역 상태를 위해 사용한다. 코드의 여러 부분에서 수정가능한 하나의 객체를 직접 접근하도록 설계하면 예외가 발생하기 쉬워진다.

📌 의존성 주입

Singleton pattern은 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있다.

이때, 의존성 주입(DI, Dependency Injection)을 통해 ****모듈 간의 결합을 조금 더 느슨하게 만들어 이를 해결할 수 있다.

의존성주입

위의 그림처럼 메인 모듈이 직접 하위 모듈에 대한 의존성을 주는 것이 아니라, 중간에 의존성 주입자를 도입하여 메인 모듈이 간접적으로 의존성을 주입하게 한다.

이를 통해 메인 모듈은 하위 모듈에 대한 의존성이 떨어지게 된다. 이를 ‘디커플링이 된다’라고 한다.

📍 장점

모듈을 쉽게 교체할 수 있기 때문에 테스팅에 유리하다.

또한 애플리케이션 의존성 방향이 일관되고, 모듈간의 관계들이 조금 더 명확해진다.

📍 단점

모듈들이 더욱 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있다.

📍의존성 주입 원칙

“상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다.”

“둘 다 추상화에 의존해야 하며, 추상화는 세부 사항에 의존하지 말아야 한다.”

📌 React의 상태 관리

React에서는 전역 상태 관리를 위해 Singleton 객체를 만드는 대신 상태 관리 도구를 사용한다.

Singleton은 인스턴스의 값을 직접 수정할 수 있는 반면에, 언급한 도구들은 읽기 전용 상태를 제공한다.

이를 통해 컴포넌트가 직접 상태를 업데이트하게 두는 것이 아니라 개발자가 의도한대로만 수정되도록 한다.

참고

면접을 위한 CS 전공지식 노트 -주홍철
JS Patterns - https://www.patterns.dev/posts/singleton-pattern

profile
성장하는 웹 프론트엔드 개발자 입니다.

0개의 댓글