본문 바로가기

클래스 기본 문법

클래스의 속성·생성자·메서드로 인스턴스를 만들고 접근 제어자 축약과 static 멤버의 소속 범위를 익힙니다.

자바스크립트는 본래 프로토타입 기반 객체 지향 언어입니다.

다만 ES2015(ES6)부터는 다른 객체 지향 언어와 비슷한 클래스(Class) 문법을 지원하기 시작했습니다.

클래스는 객체 지향의 핵심 개념인 추상화, 캡슐화, 상속, 다형성을 표현하기 위한 문법적 장치입니다.

타입스크립트는 여기에 강력한 타입 시스템을 결합해 클래스를 더 안전하고 견고하게 사용할 수 있게 해줍니다.

객체와 인터페이스의 속성·메서드와 this의 호출 규칙을 바탕으로 읽습니다. 각 코드 블록은 독립된 예제입니다. 먼저 Person을 생성하고 속성을 읽은 뒤, 생성자·setter가 서로 다른 시점에 어떤 코드를 실행하는지 확인합니다.


클래스 선언과 인스턴스 생성

클래스 인스턴스 생성 생명주기 — 상태 추적

new Person에서 this 초기화 뒤 독립 person1/person2의 값과 후속 setter/greet 결과가 바뀐다. 생성자와 setter 검증이 공유되지 않는 점을 같은 객체 정체성으로 표현할 수 있다.

new Person 호출은 생성자로 초기화한 인스턴스를 반환합니다. person1과 person2의 상태는 독립이고 getter와 setter는 구현된 코드를 실행합니다.

TYPE / RUNTIME / CONTRACT

new가 생성자를 실행해 각 인스턴스 상태를 만듭니다

Person의 생성자는 전달된 name·age·job을 각 객체에 저장합니다. 초기화했다고 해서 입력 값의 유효성을 자동 검사하는 것은 아닙니다.

호출에서 생성된 인스턴스로newPerson호출→constructor직접초기화→별도인스턴스의3노드2간선이다. getter와setter는생성의필수단계가아니며후속접근시실행된다. 생성자의검사와setter검사는자동공유되지않는다.
new Personname · age · job
연결 관계new Person · name · age · job → constructor · this의 상태 초기화
constructorthis의 상태 초기화
연결 관계constructor · this의 상태 초기화 → 새 인스턴스 · 후속 메서드 · 접근자
새 인스턴스후속 메서드 · 접근자
호출에서 생성된 인스턴스로 · 모바일newPerson호출→constructor직접초기화→별도인스턴스의3노드2간선이다. getter와setter는생성의필수단계가아니며후속접근시실행된다. 생성자의검사와setter검사는자동공유되지않는다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
new Personname · age · job
연결 관계new Person · name · age · job → constructor · this의 상태 초기화
constructorthis의 상태 초기화
연결 관계constructor · this의 상태 초기화 → 새 인스턴스 · 후속 메서드 · 접근자
새 인스턴스후속 메서드 · 접근자

TWO INSTANCES

서로 다른 name·age·job

김철수·30·개발자와 이영희·25·디자이너를 저장한 객체 두 개가 생성됩니다. person2.job 변경은 person1의 상태를 바꾸지 않습니다.

ACCESSORS

속성 접근으로 코드 실행

person2.job = "마케터"는 setter를 실행합니다. 길이 2인 SE는 newJob.length < 2 조건에 걸리지 않아 변경됩니다.

PARAMETER PROPERTY

선언과 할당을 줄이는 별도 문법

CompactPerson 생성자의 public·private 매개변수는 속성을 동시에 선언하고 초기화합니다. 모든 클래스 생성에 이 문법이 필수인 것은 아닙니다.

VALIDATION

검증은 작성한 로직에 따릅니다

Person 생성자에는 age·job 유효성 검사가 없습니다. setter는 길이만 확인하고 실패 시 로그 후 변경을 건너뜁니다. 타입 표기가 런타임 검증을 추가하지 않습니다.

필드·생성자·메서드는 역할이 다릅니다. getter·setter와 매개변수 속성을 생성의 필수 연속 단계로 보지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명new Person 호출→constructor 직접 초기화→별도 인스턴스의 노드 3개·간선 2개다. getter와 setter는 생성의 필수 단계가 아니며 후속 접근 시 실행된다. 생성자의 검사와 setter 검사는 자동으로 공유되지 않는다.
전체 관계·조건 설명new Person 호출→constructor 직접 초기화→별도 인스턴스의 노드 3개·간선 2개다. getter와 setter는 생성의 필수 단계가 아니며 후속 접근 시 실행된다. 생성자의 검사와 setter 검사는 자동으로 공유되지 않는다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
원도표 설명person2.job = "마케터"는 setter를 실행합니다. 길이 2인 SE는 newJob.length < 2 조건에 걸리지 않아 변경됩니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
new Personperson: 새 인스턴스 / 초기화: name·age·job호출값으로 constructor가 해당 this를 초기화한다getter와 setter는 생성에 반드시 필요한 단계가 아니다
독립된 두 객체person1: 김철수 유지 / person2: 독립 nameperson1과 person2는 서로 다른 상태를 읽는다같은 클래스 정의가 같은 인스턴스 상태를 뜻하지 않는다
person2 변경person1: 김철수 / person2: 지수·25·SEperson2의 name을 지수로 바꾸면 그 객체의 greet만 바뀐다setter 조건과 constructor 조건은 자동으로 공유되지 않는다

클래스 인스턴스 생성 생명주기 — 상태 추적

new Person에서 this 초기화 뒤 독립 person1/person2의 값과 후속 setter/greet 결과가 바뀐다. 생성자와 setter 검증이 공유되지 않는 점을 같은 객체 정체성으로 표현할 수 있다.

new Person 호출은 생성자로 초기화한 인스턴스를 반환합니다. person1과 person2의 상태는 독립이고 getter와 setter는 구현된 코드를 실행합니다.

TYPE / RUNTIME / CONTRACT

new가 생성자를 실행해 각 인스턴스 상태를 만듭니다

Person의 생성자는 전달된 name·age·job을 각 객체에 저장합니다. 초기화했다고 해서 입력 값의 유효성을 자동 검사하는 것은 아닙니다.

호출에서 생성된 인스턴스로newPerson호출→constructor직접초기화→별도인스턴스의3노드2간선이다. getter와setter는생성의필수단계가아니며후속접근시실행된다. 생성자의검사와setter검사는자동공유되지않는다.
new Personname · age · job
연결 관계new Person · name · age · job → constructor · this의 상태 초기화
constructorthis의 상태 초기화
연결 관계constructor · this의 상태 초기화 → 새 인스턴스 · 후속 메서드 · 접근자
새 인스턴스후속 메서드 · 접근자
호출에서 생성된 인스턴스로 · 모바일newPerson호출→constructor직접초기화→별도인스턴스의3노드2간선이다. getter와setter는생성의필수단계가아니며후속접근시실행된다. 생성자의검사와setter검사는자동공유되지않는다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
new Personname · age · job
연결 관계new Person · name · age · job → constructor · this의 상태 초기화
constructorthis의 상태 초기화
연결 관계constructor · this의 상태 초기화 → 새 인스턴스 · 후속 메서드 · 접근자
새 인스턴스후속 메서드 · 접근자

TWO INSTANCES

서로 다른 name·age·job

김철수·30·개발자와 이영희·25·디자이너를 저장한 객체 두 개가 생성됩니다. person2.job 변경은 person1의 상태를 바꾸지 않습니다.

ACCESSORS

속성 접근으로 코드 실행

person2.job = "마케터"는 setter를 실행합니다. 길이 2인 SE는 newJob.length < 2 조건에 걸리지 않아 변경됩니다.

PARAMETER PROPERTY

선언과 할당을 줄이는 별도 문법

CompactPerson 생성자의 public·private 매개변수는 속성을 동시에 선언하고 초기화합니다. 모든 클래스 생성에 이 문법이 필수인 것은 아닙니다.

VALIDATION

검증은 작성한 로직에 따릅니다

Person 생성자에는 age·job 유효성 검사가 없습니다. setter는 길이만 확인하고 실패 시 로그 후 변경을 건너뜁니다. 타입 표기가 런타임 검증을 추가하지 않습니다.

필드·생성자·메서드는 역할이 다릅니다. getter·setter와 매개변수 속성을 생성의 필수 연속 단계로 보지 않습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
전체 관계·조건 설명new Person 호출→constructor 직접 초기화→별도 인스턴스의 노드 3개·간선 2개다. getter와 setter는 생성의 필수 단계가 아니며 후속 접근 시 실행된다. 생성자의 검사와 setter 검사는 자동으로 공유되지 않는다.
전체 관계·조건 설명new Person 호출→constructor 직접 초기화→별도 인스턴스의 노드 3개·간선 2개다. getter와 setter는 생성의 필수 단계가 아니며 후속 접근 시 실행된다. 생성자의 검사와 setter 검사는 자동으로 공유되지 않는다. 모바일에서도 노드와 연결 방향·독립 경로가 같다.
원도표 설명person2.job = "마케터"는 setter를 실행합니다. 길이 2인 SE는 newJob.length < 2 조건에 걸리지 않아 변경됩니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
new Personperson: 새 인스턴스 / 초기화: name·age·job호출값으로 constructor가 해당 this를 초기화한다getter와 setter는 생성에 반드시 필요한 단계가 아니다
독립된 두 객체person1: 김철수 유지 / person2: 독립 nameperson1과 person2는 서로 다른 상태를 읽는다같은 클래스 정의가 같은 인스턴스 상태를 뜻하지 않는다
person2 변경person1: 김철수 / person2: 지수·25·SEperson2의 name을 지수로 바꾸면 그 객체의 greet만 바뀐다setter 조건과 constructor 조건은 자동으로 공유되지 않는다

클래스를 선언할 때는 class 키워드를 사용하며, 일반적으로 클래스 이름은 파스칼 케이스(PascalCase, 첫 글자 대문자)를 사용합니다.

// 'Person'이라는 이름의 클래스를 선언합니다.
class Person {
  // 1. 속성 (Property): 클래스의 데이터를 나타냅니다.
  // 각 속성에도 타입을 명시합니다.
  name: string;
  age: number;
  private _job: string; // private 접근 제어자를 가진 속성

  // 2. 생성자 (Constructor): 클래스의 인스턴스가 생성될 때 호출됩니다.
  // 인스턴스의 속성들을 초기화하는 역할을 합니다.
  constructor(name: string, age: number, job: string) {
    this.name = name; // this는 현재 생성되는 인스턴스를 가리킵니다.
    this.age = age;
    this._job = job;
  }

  // 3. 메서드 (Method): 클래스의 동작(함수)을 나타냅니다.
  // 매개변수와 반환 값에 타입을 명시합니다.
  greet(): string {
    return `안녕하세요, ${this.name}입니다. 저는 ${this.age}살이고 ${this._job}입니다.`;
  }

  // Getter (속성 값을 읽을 때 사용)
  get job(): string {
    return this._job;
  }

  // Setter (속성 값을 설정할 때 사용)
  set job(newJob: string) {
    if (newJob.length < 2) {
      console.log("직업명은 2글자 이상이어야 합니다.");
      return;
    }
    this._job = newJob;
  }
}

// 클래스의 인스턴스 (객체) 생성
// new 키워드와 함께 생성자를 호출합니다.
const person1 = new Person("김철수", 30, "개발자");
const person2 = new Person("이영희", 25, "디자이너");

console.log(person1.name); // 김철수
console.log(person1.age);  // 30
console.log(person1.greet()); // 안녕하세요, 김철수입니다. 저는 30살이고 개발자입니다.

// Getter 사용
console.log(person2.job); // 디자이너

// Setter 사용
person2.job = "마케터";
console.log(person2.greet()); // 안녕하세요, 이영희입니다. 저는 25살이고 마케터입니다.

person2.job = "SE"; // 길이가 2이므로 setter의 검사를 통과해 변경됩니다.
console.log(person2.job); // SE

클래스의 기본적인 구성 요소는 다음과 같습니다.

  • 속성 (Properties): 클래스의 상태를 나타내는 변수입니다. 각 속성에도 타입을 명시합니다. (예: name: string;)
  • 생성자 (Constructor): new 키워드를 통해 클래스의 인스턴스를 생성할 때 호출되는 특별한 메서드입니다. 인스턴스의 초기 상태를 설정하는 역할을 합니다.
  • 메서드 (Methods): 클래스가 수행할 수 있는 동작을 나타내는 함수입니다. 일반 함수와 마찬가지로 매개변수와 반환값에 타입을 명시할 수 있습니다.
  • this 키워드: 생성자와 인스턴스를 receiver로 호출한 메서드에서는 해당 인스턴스를 가리킵니다. 정적 메서드나 분리해서 전달한 메서드의 호출 규칙은 다릅니다.
  • Getter/Setter: 특정 속성에 대한 읽기/쓰기 접근을 제어하는 메서드입니다. 속성처럼 접근하지만 내부적으로는 함수가 실행됩니다.

접근 제어자

타입스크립트의 클래스는 속성과 메서드의 접근 수준을 제어하는 세 가지 접근 제어자(Access Modifiers)를 제공합니다.

이는 객체 지향의 캡슐화(Encapsulation)를 구현하는 데 중요한 역할을 합니다.

public (공개): 가장 기본적인 접근 제어자입니다.

선언된 속성이나 메서드가 클래스 외부에서 자유롭게 접근 가능함을 의미합니다.

아무런 접근 제어자를 명시하지 않으면 기본적으로 public으로 간주됩니다.

class PublicExample {
  public value: number; // public 명시
  message: string;      // 기본값 public

  constructor(val: number, msg: string) {
    this.value = val;
    this.message = msg;
  }

  public display(): void {
    console.log(`Value: ${this.value}, Message: ${this.message}`);
  }
}

const pe = new PublicExample(10, "Hello");
console.log(pe.value);   // 10 (접근 가능)
pe.display();            // Value: 10, Message: Hello (접근 가능)

private (비공개): 선언된 속성이나 메서드가 해당 클래스 내부에서만 접근 가능함을 의미합니다.

클래스 외부나 상속받은 자식 클래스에서도 접근할 수 없습니다.

이는 특정 데이터를 외부에서 직접 변경하지 못하도록 보호할 때 유용합니다.

class PrivateExample {
  private secretCode: string; // private 속성

  constructor(code: string) {
    this.secretCode = code;
  }

  public revealCode(): string {
    return `비밀 코드: ${this.secretCode}`; // 클래스 내부에서 접근 가능
  }
}

const pve = new PrivateExample("MY_PRIVATE_KEY");
// console.log(pve.secretCode); // Error: Property 'secretCode' is private and only accessible within class 'PrivateExample'.
console.log(pve.revealCode()); // 비밀 코드: MY_PRIVATE_KEY (public 메서드를 통해 간접 접근)

자바스크립트의 # 프라이빗 필드 문법과 유사하나, 타입스크립트의 private는 컴파일 시점에 검사되는 타입 시스템 레벨의 제약입니다.

protected (보호된): 선언된 속성이나 메서드가 해당 클래스 내부와 이 클래스를 상속받은 자식 클래스 내부에서만 접근 가능함을 의미합니다.

클래스 외부에서는 접근할 수 없습니다.

이는 부모 클래스의 특정 속성이나 메서드를 자식 클래스에서 활용해야 하지만, 외부 노출은 막고 싶을 때 사용됩니다.

class ProtectedExample {
  protected protectedValue: string; // protected 속성

  constructor(value: string) {
    this.protectedValue = value;
  }

  protected getProtectedValue(): string {
    return this.protectedValue;
  }
}

class ChildExample extends ProtectedExample {
  constructor(value: string) {
    super(value); // 부모 클래스의 생성자 호출
  }

  public printProtected(): void {
    console.log(`자식에서 접근: ${this.protectedValue}`);     // 자식 클래스에서 접근 가능
    console.log(`자식에서 메서드 접근: ${this.getProtectedValue()}`); // 자식 클래스에서 메서드 접근 가능
  }
}

const child = new ChildExample("보호된 값");
child.printProtected(); // 자식에서 접근: 보호된 값, 자식에서 메서드 접근: 보호된 값
// console.log(child.protectedValue); // Error: Property 'protectedValue' is protected and only accessible within class 'ProtectedExample' and its subclasses.

생성자의 매개변수에 접근 제어자 사용하기

타입스크립트에서는 생성자의 매개변수에 직접 접근 제어자(public, private, protected, readonly)를 붙여서, 해당 매개변수를 클래스의 속성으로 동시에 선언하고 초기화할 수 있는 편리한 문법을 제공합니다.

class CompactPerson {
  // 매개변수에 public을 붙여 자동으로 속성으로 선언하고 초기화
  constructor(public name: string, public age: number, private _secretInfo: string) {
    // this.name = name; 이런 식으로 속성 할당 코드를 생략할 수 있습니다.
  }

  get secretInfo(): string {
    return this._secretInfo;
  }
}

const compactP = new CompactPerson("김효진", 28, "비밀 일기");
console.log(compactP.name);       // 김효진
console.log(compactP.age);        // 28
// console.log(compactP._secretInfo); // Error: private 속성이므로 접근 불가
console.log(compactP.secretInfo); // 비밀 일기 (getter를 통해 접근)

이 방식은 속성 선언과 생성자 초기화 코드를 줄입니다. 생성자 매개변수에 이름만 쓰면 지역 매개변수이고, public·private·protected 또는 readonly를 붙인 매개변수 속성은 this의 멤버로도 저장됩니다. 이 축약이 값 검증을 자동 추가하는 것은 아닙니다.


static 속성과 메서드

클래스의 static (정적) 멤버는 클래스의 인스턴스가 아닌, 클래스 자체에 속하는 속성이나 메서드입니다.

즉, new 키워드로 인스턴스를 생성하지 않고도 클래스 이름으로 직접 접근할 수 있습니다.

유틸리티 함수나 모든 인스턴스가 공유해야 하는 상수 값 등을 정의할 때 유용합니다.

class MathUtil {
  static PI: number = 3.14159; // 정적 속성

  static calculateCircleArea(radius: number): number { // 정적 메서드
    return this.PI * radius * radius; // 정적 메서드 내에서 정적 속성 접근 시 this.PI
  }

  // 인스턴스 메서드에서는 정적 멤버에 직접 접근하지 않는 것이 일반적입니다.
  // 다만 클래스 이름으로 접근은 가능합니다.
  public getInstancePi(): number {
    return MathUtil.PI; // 클래스 이름으로 접근
  }
}

// 인스턴스 생성 없이 직접 클래스 이름으로 접근
console.log(MathUtil.PI); // 3.14159
console.log(MathUtil.calculateCircleArea(5)); // 78.53975

const util = new MathUtil();
console.log(util.getInstancePi()); // 3.14159 (인스턴스를 통해 정적 속성 접근)

// 오류: 인스턴스를 통해 정적 메서드에 접근할 수 없습니다.
// util.calculateCircleArea(10); // Error: Property 'calculateCircleArea' does not exist on type 'MathUtil'. Did you mean 'static calculateCircleArea'?

static 멤버는 클래스명 뒤에 점(.)을 찍어 접근합니다.

MathUtil.calculateCircleArea()처럼 클래스 객체를 receiver로 호출한 정적 메서드에서는 this.PI로 정적 속성을 읽습니다. 인스턴스 메서드의 this와 구별합니다.


클래스는 복잡한 애플리케이션의 구조를 체계적으로 설계하고 관리하는 데 필수적인 요소입니다.

타입스크립트는 자바스크립트의 클래스 문법에 타입 안전성과 접근 제어자 같은 객체 지향적 기능을 추가하여, 더욱 견고하고 유지보수하기 쉬운 코드를 작성할 수 있도록 돕습니다.

클래스 멤버를 설계할 때는 인스턴스 상태인지, 외부 API인지, 클래스 공통 유틸리티인지 먼저 나누면 접근 제어자와 static 여부를 더 안정적으로 결정할 수 있습니다.

생성과 변경을 따로 따라가기

원 Person 예제를 모두 실행한 뒤 아래 코드를 이어서 실행합니다. 처음 생성할 때는 생성자가 _job에 직접 저장하고, 생성한 뒤 job을 대입할 때만 setter의 길이 검사를 거칩니다.

person1.job = '한'; // setter가 거절하고 기존 개발자를 유지
console.log(person1.job); // 개발자
const shortJobPerson = new Person('민지', 20, '한');
console.log(shortJobPerson.job); // 한: 생성자에는 같은 검사가 없음

첫 대입은 경고를 출력한 뒤 return하여 _job을 바꾸지 않습니다. 다음 생성자는 문자열 한을 직접 저장하므로 job getter도 한을 반환합니다. 입력 경로마다 지켜야 하는 조건이 있다면 생성과 변경 양쪽에서 같은 검증을 사용하도록 설계해야 합니다. 이 예제는 그 검증을 완성한 모델이 아닙니다.

혼자 소속과 결과 확인하기

원 예제를 모두 실행한 상태에서 person2.name = '지수'로 바꾼 뒤 person1.name과 person2.greet()를 예측하세요. 별도 MathUtil 예제에서 MathUtil.PI = 3을 대입하면 util.getInstancePi()의 값은 무엇일까요? util.PI로 읽을 수 있는지도 타입 검사를 해 봅니다.

인스턴스와 클래스 멤버 해설

person1.name은 김철수로 유지됩니다. person2는 자기 name만 바뀌고 원 예제 마지막 job은 SE이므로 인사말은 안녕하세요, 지수입니다. 저는 25살이고 SE입니다.입니다. PI는 readonly가 아닌 static 속성이므로 MathUtil.PI에 3을 저장하면 util.getInstancePi()도 클래스 객체의 현재 값 3을 읽습니다. util.PI는 인스턴스에 선언된 멤버가 아니므로 타입 오류입니다. 두 인스턴스의 서로 다른 상태와 클래스 객체의 공유 상태를 구별합니다.

MathUtil.PI와 calculateCircleArea는 static 멤버이며 클래스 객체로 접근합니다. 인스턴스 상태와 접근 제한, JavaScript #private의 런타임 은닉을 별도로 비교합니다.

TYPE / RUNTIME / CONTRACT

멤버의 소속과 접근 범위는 서로 다른 기준입니다

인스턴스 상태는 객체에, static은 클래스 객체에 속합니다. public·protected·private는 TypeScript에서 멤버 접근을 검사합니다.

INSTANCE

객체의 상태와 메서드

Person의 name·age·job은 객체별 상태입니다. person1.greet()처럼 객체가 receiver인 호출은 그 객체의 this를 사용합니다.

STATIC

클래스 객체의 멤버

MathUtil.PI는 3.14159, 반지름 5의 면적은 78.53975입니다. util.calculateCircleArea는 인스턴스 멤버가 아니므로 오류입니다.

ACCESS

정적 접근 제한

public은 외부에서 접근할 수 있고 protected는 클래스와 파생 클래스의 허용 문맥, private는 선언 클래스의 허용 문맥에서 접근합니다. 이 선택은 instance/static 소속과 독립입니다.

#PRIVATE

JavaScript가 은닉을 강제

TypeScript private 접근 제한은 출력에서 제거되지만 JavaScript #field는 런타임에서도 강제됩니다. 본문 코드는 TypeScript private를 사용하며 #field는 비교 설명입니다.

상태 없는 기능은 모듈 함수로 표현할 수도 있습니다. 공유하는 mutable static 상태는 별도의 수명과 테스트 관리가 필요합니다.