공부/디자인 패턴

디자인 패턴 - 싱글톤

dev_jiwonpark 2025. 6. 27. 18:22

멘토님과 수업을 하면서 앞으로 수업 방향에 대한 이야기를 나눴다.
나는 어떤 공부를 하고 싶고 어떤 개발자가 되고 싶은지 그 목표에 중점을 두어 수업 방향을 논의하던중..!

디자인패턴에 대한 이야기가 나왔다. 

사실 나에게 디자인 패턴은 항상 뭐랄까.. 피하고 싶은 존재이다.

공부해야할 건 알지만 .. 하기 싫은..! 그래서 요리조리 피했던 주제이다. 항상 면접질문으로 아시는 디자인 패턴에 대해 설명해주세요! 라고 하면 외워간 것만 줄줄줄..이야기했던 것 같다.

 

그렇게 요리조리 피하다가 멘토님께서 이렇게 말씀하셨다.

" 멘티님이 원하는 개발 실력을 갖기 위해선 디자인 패턴을 공부하면 좋을것 같습니다! "

그래서 디자인 패턴 공부를 위한 사이트를 전달주셨고 단기적으로 공부하는 것이 아니라 장기적으로 디자인 패턴을 공부해보려고한다..!

 

멘토님은 왜 내게 그런 조언을 하셨을까 .. 하믄 내가 평소 개발을 하면서 가졌던 의문은 다음과 같다.

"이거 분명히 어디선가 비슷한 기능을 짰던 것 같은데..."
"이 코드... 나만 이렇게 짜는 건 아닐 텐데?"

이럴때 필요한게 디자인 패턴이라고 한다. 그래서 디자인 패턴 공부를 권장하신것 같다.

 

우선 디자인 패턴이 무엇인가 !

반복되는 문제 상황들을 최적화된 방법으로 해결하도록 돕는 개념

 

쉽게 말하자면 자주 등장하는 문제에 대해 검증된 ‘코드 설계 방법’이다. 소프트웨어 개발 역사 속에서 수많은 개발자들이 겪었던 반복적인 문제들을 어떻게 해결할지 정리한 일종의 베스트 프랙티스 모음이라고 보면 된다.

 

왜 디자인 패턴을 공부해야 할까?

  • 같은 문제를 반복해서 고민하지 않기 위해
    이미 수많은 개발자들이 수십 년 동안 경험한 문제라면, 나도 그 해결 방법을 참고해서 더 빠르게, 더 깔끔하게 해결할 수 있어야 하지 않을까?
  • 협업할 때 통하는 ‘공용 언어’가 되기 때문
    “이거 싱글톤으로 처리했어”라고 말했을 때, 상대가 바로 이해할 수 있다면 더 적은 말로도 코드를 설명할 수 있다. 디자인 패턴은 단순한 기법이 아니라 개발자들끼리의 의사소통 도구이기도 하다.
  • 유지보수와 확장성 측면에서 유리하다
    잘 짜인 디자인 패턴은 나중에 기능을 추가하거나 수정할 때도 구조를 건드리지 않고 변화에 유연하게 대응할 수 있다.

 

그리고 디자인 패턴에는 목적에 따라 여러 가지 종류가 있다.
예를 들면, 객체를 어떻게 만들까? 에 집중한 패턴도 있고,
"객체들끼리 어떻게 상호작용할까?"에 중점을 둔 패턴도 있다!

 

그중에서도 이번 게시글에서는 가장 기본적이면서도 중요한,
객체를 ‘어떻게 생성할지’에 초점을 둔 패턴들부터 먼저 짚고 가보려고 한다!

이걸 "생성 패턴(Creational Pattern)"이라고 부른다

 

생성 패턴 이란?

더 풀어서 설명하자면 ! 

단순히 new 키워드를 이용해 객체를 직접 생성하는 방식은 간단하지만, 애플리케이션이 커지거나 복잡해질수록 유지보수와 확장에 한계를 드러내게 된다.

이러한 한계를 극복하기 위해 객체 생성 과정을 캡슐화하고, 유연하게 관리할 수 있도록 도와주는 것이 바로 생성 패턴의 핵심 역할이다.

 

왜 객체 생성에도 신경을 써야 하는가?

객체를 생성하는 방식은 소프트웨어 아키텍처 전반에 큰 영향을 미치게 된다.
특정 클래스가 다른 클래스에 강하게 의존하고 있다면, 그만큼 코드의 유연성은 떨어지고, 테스트나 확장 시에도 불편함이 따르게 된다..!ㅠ

생성 패턴은 다음과 같은 장점이 존재한다.

  • 코드의 결합도를 낮춰 유지보수가 쉬워지고
  • 변경에 유연하게 대응할 수 있으며
  • 의존성 주입과 같은 기법과 결합하여 테스트 가능한 구조를 설계할 수 있다. 

결과적으로, 객체를 단순히 '생성하는 행위' 이상으로 바라보고,
언제, 어디서, 어떻게 생성할지를 체계적으로 설계하는 것
더 견고하고 확장성 있는 프로그램을 만드는 데 도움이 되는 것 !

 

대표적인 생성 패턴

  • Singleton (싱글톤)
  • Factory Method (팩토리 메서드)
  • Abstract Factory (추상 팩토리)
  • Builder (빌더)
  • Prototype (프로토타입)

이번 게시글에서는 싱글톤에 대해서 작성해보려고한다.

우선 싱글톤을 공부하면서 이게 어디에 쓰일꼬.. 찾아보던중 

supabase로 auth 연동을 하면서 싱글톤이 쓰이는 곳을 발견하게 되어 아! 이런 상황에 쓰이는구나 ! 라고 인지한 경험이 있다.
(supabase로 auth 연동하는 과정은 나중에 게시글로 남겨보겠다..!)

https://supabase.com/docs/guides/auth/server-side/nextjs

 

Setting up Server-Side Auth for Next.js | Supabase Docs

Be careful when protecting pages. The server gets the user session from the cookies, which can be spoofed by anyone. Always use supabase.auth.getUser() to protect pages and user data. Never trust supabase.auth.getSession() inside Server Components. It isn'

supabase.com

위 링크에서 guide를 보던 중 아래 문장이 발견했다.

클라이언트에서 createBrowserClient는 이미 싱글톤 패턴을 사용하므로 createClient 함수를 몇 번 호출하든 인스턴스는 한 번만 생성됩니다.

 

처음 이 내용을 읽었을 때, 이런 의문이 들었다.

“왜 클라이언트에서는 싱글톤을 쓰고, 서버에서는 매번 새로 만들어야 하지?”

이 질문의 답을 찾다 보니 자연스럽게 ‘싱글톤 패턴(Singleton Pattern)’ 이라는 개념을 이해할 수 있게 되었다.

 

자 이걸 이해하기 전에 싱글톤이 무엇인가를 찾아보면 아래와 같다.

어플리케이션 전체에서 단 하나의 인스턴스만 만들고, 모두가 그 하나만을 공유해서 쓰게 하는 패턴

 

이제 앞서 본 Supabase 의 가이드 내용을 싱글톤 관점에서 다시 정리해보면 다음과 같다.

1. 클라이언트 환경 (브라우저)

  • React/Next.js의 클라이언트 환경은 싱글 스레드이며 하나의 실행 컨텍스트를 유지함.
  • 페이지를 이동해도 앱은 메모리 위에 계속 존재하기 때문에, Supabase 인스턴스를 매번 새로 만들 필요가 없음.
  • 오히려 싱글톤으로 관리하면 메모리도 효율적으로 쓰이고, 세션 정보도 일관되게 유지할 수 있음.
  • 그래서 createBrowserClient()는 내부적으로 싱글톤 패턴을 적용하고 있음.

2. 서버 환경 (SSR, 미들웨어 등)

  • 서버는 사용자 요청(Request)마다 새로운 실행 컨텍스트가 생성됨.
  • 사용자 A와 B의 요청은 철저히 분리되어야 하며, 공유된 인스턴스를 쓰면 세션/쿠키가 섞일 수 있음
  • 보안상 치명적인 문제가 발생할 수 있기 때문에, 서버에서는 매 요청마다 Supabase 인스턴스를 새로 생성해야 함.

때문에 싱글톤은 공통된 리소스를 여러 곳에서 안정적으로 공유할 필요가 있을 때 유용한 패턴이라는것을 알게 되었다.

결국 싱글톤을 사용할지 말지는 실행 환경의 특성과 목적에 따라 달라진다는 것을 Supabase 사례를 통해 직접 확인할 수 있었다!

 

그럼 싱글톤 개념을 어느정도 읽었으니 예시를 통해 체험해보면 좋을 것 같아. 예시 코드를 몇개 준비했다!

두가지 케이스로 싱글톤을 이해하면 좋을 것 같다.

 

Case1. 단순 싱글톤 - 인스턴스는 하나지만, 값은 외부에서 바뀔 수 있음.

class MySingle {
  static instance: MySingle;
  public value = 0; // 프로퍼티

  private constructor() {}

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

const s1 = MySingle.getInstance();
const s2 = MySingle.getInstance();

console.log(s1 === s2); // true (같은 인스턴스)

s1.value = 10;         // 외부에서 값 변경
console.log(s2.value); // 10 (같은 객체라 값이 공유됨)

이 예제에서는 getInstance()를 통해 항상 같은 인스턴스를 반환하므로 s1과 s2는 동일한 객체다.

하지만 value라는 속성은 외부에서 자유롭게 변경 가능하기 때문에, 의도치 않게 값을 수정하는 실수가 발생할 수 있음..!

Case2. Object.freeze()로 동결된 싱글톤 – 인스턴스를 보호하는 방법

class MySingle {
  static instance: MySingle;
  public value = 0;

  private constructor() {}

  static getInstance() {
    if (!MySingle.instance) {
      MySingle.instance = new MySingle();
      Object.freeze(MySingle.instance); // 객체 동결
    }
    return MySingle.instance;
  }
}

const s1 = MySingle.getInstance();
s1.value = 10;          // 변경 시도 → 무시됨
console.log(s1.value);  // 0 (값이 바뀌지 않음)

여기서는 Object.freeze()를 사용해 인스턴스를 불변(immutable)으로 만들었음.
이렇게 하면 외부에서 실수로 값을 수정하는 버그를 방지할 수 있음.

 

왜 freeze를 사용할까?

싱글톤으로 관리하는 인스턴스는 전역적으로 공유되기 때문에, 한 곳에서 잘못 수정하면 애플리케이션 전체에 영향을 줄 수 있다.

이런 문제를 예방하기 위해 Object.freeze()를 사용하여 인스턴스의 상태를 보호하고, 실수로 값이 바뀌는 상황을 차단할 수 있다!!

 

여기까지 싱글톤 패턴에 대해서 공부한 내용을 기록하려고 한다..!
아래에는 참고한 사이트를 작성해두겠습니다

 

Reference

https://patterns-dev-kr.github.io/design-patterns/introduction/

 

Design Pattern 소개

📜 원문: patterns.dev - design pattern introduction 디자인패턴은 소프트웨어를 개발하는 과정의 반복되는 일반적인 문제들에 대해 기준이 되는 해결책을 제공하는 중요한 개념이다. 디자인패턴은 소프

patterns-dev-kr.github.io