싱글톤 패턴

지인혁·2023년 11월 16일
0
post-thumbnail

🤔 들어가며

싱글톤 패턴... 정말 오랜만에 마주하는 디자인 패턴이였다. 학부 시절 교수님께서 많은 디자인 패턴이 있다고 알려주었고 그 중에서 싱글톤 패턴에 한 개만 교육해주었다. 그때가 아마도 첫 디자인 패턴을 사용해 본 경험이였고 정말 유용하다고 생각했엇다.

그러다 데브코스 과제 노션 프로젝트에서 라우트 기능을 작성해야했고 라우트 기능은 모든 컴포넌트에서 사용할 수 있어야 했다.

방법으로는 최상위 컴포넌트에서 라우트 기능을 구현하고 모든 컴포넌트에게 props로 전달 전달하는 방식이 있었지만 이는 너무 비효율적이다. 그리고 커스텀 이벤트를 통해 전역적으로 이벤트를 핸들하면서 라우트 기능을 사용하는 방법도 있었지만 멘토님께서 이 방법은 실무에서는 잘 사용되지 않는다 말과 뭔가 window 전역 객체의 역할이 더 많이지면서 다른 방법을 찾아봤다.

그래서 곰곰히 생각하다가 싱글톤 패턴이 생갔이 났고 오히려 더 가독성 좋고 컴포넌트 사이 결합도도 낮출 수 있을 것 같아서 싱글톤 패턴으로 라우트 함수를 구현했다. 그러다 싱글톤 패턴을 다시 사용하다가 익숙치 않아서 다시 정리할려고 한다.


싱글톤(Singleton Pattern) 패턴

싱글톤 패턴(Singleton Pattern)은 소프트웨어 디자인 패턴 중 하나로, 특정 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 클래스의 인스턴스를 새로 생성하지 않고 단 하나만의 인스턴스에 접근할 수 있도록 하는 패턴이다.

싱글톤 패턴은 주로 공유 자원에 대한 동시 접근을 제어하거나, 한 시스템에서 유일하게 존재해야 하는 기능을 구현하는 데 사용된다. 나는 라우트 기능을 싱글톤 패턴으로 구현하였고 라우트 기능은 시스템에서 전역적으로 어디서에든 사용된다.

예시로 라우트 클래스를 만들고 기능을 사용할려면 일반적으로 new 키워드를 통해 인스턴스를 계속 생성해줘야 한다.

const route = new Route();

하지만 각 인스턴스들은 그냥 동일한 기능을 수행하고 인스턴스를 새로 생성할때 마다 메모리가 낭비된다.

싱글톤 패턴을 통해 첫 인스턴스를 만들어두고 계속해서 첫 인스턴스를 재사용하며 하나의 인스턴스만 사용하여 메모리 낭비를 줄일 수 있다.


구현(라우트 기능)

import RootPage from '../Page/RootPage.js';
import EditPage from '../Page/EditPage.js';
import Sidebar from '../Component/Sidebar/Sidebar.js';

export default class RouterManger {
    static instance = null;

    static getInstance() {
        if (!RouterManger.instance) {
            RouterManger.instance = new RouterManger();
        }
        return RouterManger.instance;
    }

    constructor() {
        if (RouterManger.instance) {
            return RouterManger.instance;
        }
        this.$target = document.querySelector('#app');

        this.init();
    }

    init() {
        new Sidebar({ $target: this.$target });
        window.onpopstate = () => this.route();
    }

    changeUrl(url) {
        history.pushState(null, null, url);
        this.route();
    }

    route() {
        const { pathname } = window.location;
        const $last = this.$target.lastElementChild;

        if ($last.className !== 'sidebar') {
            this.$target.removeChild($last);
        }

        if (pathname === '/index.html' || pathname === '/') {
            return new RootPage({ $target: this.$target });
        }

        if (pathname.indexOf('/document') === 0) {
            const documentId = pathname.split('/')[2];

            return new EditPage({ $target: this.$target, initalState: { documentId } });
        }

        new RootPage({ $target: this.$target });
    }
}

먼저 static 키워드는 해당 클래스에 속한 정적 메소드나 정적 속성을 정의하는데 사용된다. 정적 메소드나 정적 속성은 클래스의 인스턴스가 아니라 클래스 자체에 속하며, 클래스의 이름을 통해 직접 접근할 수 있습니다.

인스턴스를 생성하지 않고 클래스 이름을 통해 접근할 수 있는 메소드나 속성을 사용할 수 있고 해당 클래스의 모든 인스턴스에서 공유되는 값을 설정하는 키워드다 static이다.

그럼 어떻게 사용해야할까??

싱글톤 패턴을 사용할 때 일반적인 new 키워드로 인스턴스를 생성하는 방법과는 다르다.

RouterManger.getInstance().changeUrl(`/`);

클래스명의 getInstance 메소드 핵심이다. 초기 instance 속성이 null이고 첫 getInstance 호출 시 instance 속성이 null일 때 new 키워드로 첫 인스턴스 객체를 생성해 준다.

후에 생성된 인스턴스 객체를 instance 속성에 값을 저장하고 return 해줌으로 써 첫 인스턴스 객체가 반환된다.

그럼 instance 객체는 null이 아니고 초기에 만들어진 인스턴스 객체를 저장하게 되고 getInstance 메소드가 호출하게 되면 instance 객체가 null이 아니라 이미 만들어진 instance 에 담긴 인스턴스 객체를 반환하게 됨으로 하나의 인스턴스 객체만을 사용하게 된다.

이게 싱글톤 패턴의 셋팅?? 이며 인제 원하는 컴포넌트에서

RouterManger.getInstance().changeUrl(`/`);
RouterManger.getInstance().init();
RouterManger.getInstance().route();

이러한 형식으로 원하는 메서드을 호출하여 사용하면 된다.


👍 마치며

잊고 있던 싱글톤 패턴을 다시 사용하면서 이번 노션 프로젝트에서 정말 유용하게 사용한 경험이었다. 앞으로도 정말 자주 사용될 디자인 패턴일 것? 같다는 생각이 들었다.

확실히 컴포넌트 간의 결합도를 효과적으로 줄일 수 있었고 다른 디자인 패턴도 공부하며 점차 성장해야겠다는 생각이 든다. 아마 다음 포스팅은 옵저버 패턴에 대한 내용인데 옵저버 패턴과 싱글톤 패턴들 혼합하여 사용할 예정이다.

profile
대구 사나이

0개의 댓글