디자인 패턴 - 싱글톤 패턴

강아G·2022년 9월 10일
0

글또

목록 보기
9/10
post-thumbnail

0. 들어가며

최근 디자인 패턴에 대해 호기심이 생겨 찾아보게 되었다.
우선 디자인 패턴이란 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책이다.
다른 말로 하자면, 프로그래머가 어플리케이션이나 시스템을 디자인 할 때 공통된 문제들을 해결하는 데 쓰이는 형식화 된, 가장 좋은 관행이다.


1. 싱글톤 패턴이란?

그럼 싱글톤 패턴이란 무엇일까?
싱글톤 패턴이란 생성자(인스턴스)가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고, 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.
코드로 보는 게 더 이해하기가 수월할 것 같아서 아래의 코드를 준비해보았다.


2. 코드

2-1) 싱글톤 패턴이 아닌 경우

export default function notSingleton() {
// singleton이 아닌 코드
// Counter의 인스턴스를 여러 번 만들 수 있으므로!
    let count = 0

    class Counter {
        getInstance() {
            return this
        }

        getCount() {
            return count
        }

        increment() {
            return count++
        }

        decrement() {
            return count--
        }
    }

    const counter1 = new Counter()
    const counter2 = new Counter()


// 즉, counter1과 counter2는 각각 별개의 인스턴스를 가리킨다.
// 각 인스턴스의 getInstance 메서드를 호출해 반환되는 레퍼런스를 다르다.
    console.log(counter1.getInstance() === counter2.getInstance()) // false
}

주석으로도 표현해두었지만, 위의 코드는 싱글톤 패턴이 아니다.
왜냐하면 new Counte를 통해 생성한 인스턴스를 비교해보면, 같지 않다고(false 값이) 나오기 때문이다.

싱글톤 패턴은 아래처럼 구현한다.

2-2) 싱글톤 패턴(클래스)

export default function singleton() {
    // notSignleton을 singleton으로 수정

    let instance
    let count = 0

    class Counter {
        constructor() {
            if(instance) {
                throw new Error('You ca only create one instance!')
            }
            instance = this
        }

        getInstance() {
            return this
        }

        getCount() {
            return count
        }

        increment() {
            return count++
        }

        decrement() {
            return count--
        }
    }

    // const counter1 = new Counter()
    // const counter2 = new Counter()
    // console.log(counter1.getInstance() === counter2.getInstance()) // Error occur

    // 인스턴스를 freeze해서 객체를 사용하는 쪽에서 직접 객체를 수정할 수 없도록 해준다.
    // 이 처리로 인해 프로퍼티 추가 및 수정이 불가하므로 Singletone 인스턴스의 프로퍼티를 덮어쓰는 실수를 예방할 수 있다.
    const singletonCounter = Object.freeze(new Counter()) // Counter {}
    console.log(singletonCounter)
}

위에서 보면 counter1과 counter2는 같은 인스턴스를 가리키고 있다.

추가적으로 Object.freeze 메서드를 통해,
Count를 사용하는 곳에서 기존에 생성된 인스턴스를 수정할 수 없게 만들었다.
다른 값으로 덮어 쓰이지 않기 때문에 훨씬 안전하게 사용할 수 있다.

2-3) 싱글톤 패턴(객체 리터럴)

자바스크립트에서는 위의 방법처럼 클래스를 사용하지 않고도 싱글톤 패턴을 구현할 수 있다.
객체 리터럴을 이용하면 되는데, 코드는 아래와 같다.

export default function singletonObj() {
    // 다른 언어들과는 달리 js에서는 클래스 대신 객체 리터럴을 사용하여 싱글톤 패턴을 구현할 수도 있다.

    let count = 0;

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

    Object.freeze(counter);
    console.log(counter.getInstance()) // { increment: [Function: increment], decrement: [Function: decrement] }
}

3. 장점과 단점

싱글톤 패턴의 장점으로는 메모리를 절약할 수 있다는 점이다.
인스턴스를 하나만 만들도록 강제하기 때문에 한정된 메모리를 사용할 수밖에 없는 것이다.

다만 단점으로는 최근의 자바스크립트 기조와 달리, 싱글톤 패턴은 일반적으로 앱의 전역 상태를 위해 사용된다는 것이다.
자바스크립트에서는 ES6부터 var 대신 let과 const를 도입하여, 최대한 전역 변수 사용을 지양하고 있다.
또한 export와 import 구문을 도입하여, 전역 객체를 수정하지 않고도 모듈 내에서 전역으로 사용할 수 있는 변수를 만들도록 하였다.
다양한 개념을 도입해서 전역 상태를 지양하도록 하는 것은 전역 상태를 사용하는 것이 자바스크립트의 안티 패턴으로 꼽히기 때문이다.

전역 상태를 무분별하게 사용하게 되면, 프로젝트의 규모가 커질수록 데이터의 흐름을 파악하기 어려워지기 때문이다.
즉, 프로젝트가 커지면 커질수록 전역 상태를 참조하는 컴포넌트가 많아지게 되고 서로 참조하게 되는데 이럴 경우 디버깅이 어려워지는 것이다.
자바스크립트 UI 라이브러리인 React의 경우 전역 상태 관리를 제공하지만, 읽기 전용 상태로 제공하여 부작용을 줄이려 애썼다.


4. 마치며

React뿐만 아니고 Vue에서도 Flux 패턴을 통해 데이터의 흐름을 관리하려 한다.
그만큼 전역 상태를 관리하는 것이 최근의 프로젝트를 다룰 때 중요한 부분임을 알 수 있었다.


5. 출처

1) 디자인 시스템이란

https://ko.wikipedia.org/wiki/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4_%EB%94%94%EC%9E%90%EC%9D%B8_%ED%8C%A8%ED%84%B4

2) 싱글톤 패턴의 정의

https://ko.wikipedia.org/wiki/%EC%8B%B1%EA%B8%80%ED%84%B4_%ED%8C%A8%ED%84%B4

3) 코드 및 장단점

https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/

profile
G는 무슨 G?

0개의 댓글