research

런타임에 한 번만 생성할 것을 굳이 클래스로 만들어야 할까?

런타임에 한 번만 생성할 것을 굳이 클래스로 만들어야 할까?

핵심 질문

"프로그램 runtime 중에 한 번만 생성할 클래스를 굳이 클래스로 만들 필요가 있을까? 만약 클래스로 관리해야 한다면 효과적인 방법은 무엇인가?"

클래스가 하는 일 3가지

클래스는 원래 세 가지를 묶어준다:

  1. 상태 (state) — 데이터를 담는다
  2. 행동 (behavior) — 메서드로 그 데이터를 다룬다
  3. 정체성 (identity)instance1 is instance2 같은 개체 구분

"딱 한 번만 만들 것"이라면 정체성은 의미가 없다. 비교할 두 번째 인스턴스가 없으니까. 결국 남는 건 상태 + 행동인데, 이걸 꼭 클래스로 해야 하냐는 게 핵심 질문이다.


대안 비교

1. 모듈 레벨 변수 (Python / Go / JS)

# db.py
import sqlite3

conn = sqlite3.connect("db.sqlite")

def execute(query):
    return conn.execute(query)

Python 모듈은 첫 import 때 한 번만 실행되고 이후엔 캐싱된다. 구조 없이 사실상 싱글턴.

단순함. 오버엔지니어링 없음
Python에서 가장 관용적인 방법
인터페이스가 없어 테스트에서 mock 교체 어려움
초기화 타이밍 제어 어려움 (import되면 바로 실행)
Java/C++ 같은 언어에서는 선택지 자체가 없음

2. Singleton 패턴 (GoF)

class DatabaseManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.conn = sqlite3.connect("db.sqlite")
        return cls._instance
클래스로 캡슐화 — 메서드, 타입 힌트, 인터페이스 제공
초기화 타이밍 제어 가능 (첫 호출 때)
숨겨진 전역 상태 — 의존성이 코드에 안 보임
테스트가 어렵다 — 테스트끼리 상태 공유, mock 교체 어렵고 teardown 필요
SRP 위반 — 클래스가 자기 생명주기를 스스로 관리함

3. Dependency Injection

# 앱 시작점 (composition root)
conn = sqlite3.connect("db.sqlite")
db = DatabaseManager(conn)  # 평범한 클래스, 싱글턴 아님

# 필요한 곳에 주입
order_service = OrderService(db)
user_service = UserService(db)

DatabaseManager는 평범한 클래스다. "하나만 만들겠다"는 결정을 클래스 안에 숨기지 않고, 앱 시작점(composition root)에서 명시적으로 한다.

의존성이 보인다OrderService(db) 한 줄에서 db를 쓴다는 게 드러남
테스트가 쉽다OrderService(mock_db)로 끝
나중에 멀티 DB가 필요해지면 다른 인스턴스 넘기면 됨
인스턴스를 계속 넘겨줘야 해서 보일러플레이트 증가
큰 앱에선 IoC 컨테이너가 필요해짐

4. IoC 컨테이너 (Spring / FastAPI Depends 등)

DI를 프레임워크가 관리해주는 것. "이 타입은 싱글턴으로 관리해"를 컨테이너에게 위임.

# FastAPI 예시
def get_db():
    return db_instance  # 앱 시작 때 만들어둔 것

@router.get("/orders")
def get_orders(db: DatabaseManager = Depends(get_db)):
    ...
DI의 장점 + 보일러플레이트 감소
프레임워크 학습 비용

결론

상황추천
Python 소규모, 단순 유틸모듈 레벨 변수
테스트 필요 or 중간 이상 규모DI (composition root에서 생성)
프레임워크 쓰는 중간~대규모IoC 컨테이너
GoF Singleton특수한 경우 (Logger 정도)만

핵심 원칙: "하나만 만들겠다"는 결정을 클래스 안에 숨기지 마라. 그 결정은 앱 시작점에서 명시적으로 하는 게 낫다.


관련