Prototype 패턴
기존 객체를 복제해서 새로운 객체를 만드는 Prototype 패턴을 알아봅니다.
#똑같은 걸 처음부터 다시 만들라고?
게임을 만들고 있다고 해보자.
맵에 나무 오브젝트를 1000개 배치해야 한다. 나무마다 색상이나 크기는 조금씩 다르지만, 기본 구조는 같다.
for (let i = 0; i < 1000; i++) {
const tree = new Tree();
tree.loadModel("tree.obj"); // 파일에서 모델 로드 (느림)
tree.loadTexture("tree.png"); // 텍스처 로드 (느림)
tree.setPosition(randomX(), randomY());
tree.setScale(randomScale());
}모델 파일을 1000번 읽는다. 매번 같은 파일인데.

이미 만들어진 나무 하나를 복사해서 위치와 크기만 바꾸면 훨씬 빠르지 않을까?
이 아이디어가 바로 Prototype 패턴이다.
visible: false
Prototype이란?
기존 객체를 복제(clone) 하여 새로운 객체를 생성하는 패턴
new로 처음부터 만드는 대신, 이미 있는 객체를 복사한 뒤 필요한 부분만 수정한다.
비유하자면 문서 템플릿과 같다.
이력서를 매번 처음부터 작성하는 사람은 없다. 기존 이력서를 복사해서 내용만 수정한다.
visible: false
문제: 외부에서 복제하기 어렵다
"그냥 스프레드 연산자 쓰면 되는 거 아닌가?"
const original = new User("junbeom", 25, new Address("Seoul", "Korea"));
const copy = { ...original };
copy.address.city = "Busan";
console.log(original.address.city); // "Busan" 🤯 원본도 바뀌었다!얕은 복사(shallow copy) 문제다. 중첩된 객체는 참조만 복사되기 때문에 원본까지 영향을 받는다.
그리고 더 큰 문제가 있다. 객체에 private 필드가 있다면?
class User {
private passwordHash: string;
// 외부에서는 이 필드에 접근할 수 없어서 복제도 못 한다
}외부에서는 객체의 내부 구조를 다 알 수 없다. 그래서 객체 스스로가 자신을 복제하도록 해야 한다.
visible: false
코드로 보기
Prototype 인터페이스
interface Prototype<T> {
clone(): T;
}구현 예시: 설정 프리셋
class DashboardConfig implements Prototype<DashboardConfig> {
constructor(
public layout: string,
public theme: string,
public widgets: Widget[],
private apiKey: string, // private 필드도 복제 가능
) {}
clone(): DashboardConfig {
// 깊은 복사: 중첩된 객체도 새로 만든다
const clonedWidgets = this.widgets.map((widget) => widget.clone());
return new DashboardConfig(
this.layout,
this.theme,
clonedWidgets,
this.apiKey, // 자기 자신이라 private에 접근 가능!
);
}
}
class Widget implements Prototype<Widget> {
constructor(
public type: string,
public position: { x: number; y: number },
public size: { width: number; height: number },
) {}
clone(): Widget {
return new Widget(
this.type,
{ ...this.position }, // 중첩 객체도 새로 복사
{ ...this.size },
);
}
}사용
// 기본 대시보드 설정 (프리셋)
const defaultDashboard = new DashboardConfig(
"grid",
"dark",
[
new Widget("chart", { x: 0, y: 0 }, { width: 400, height: 300 }),
new Widget("table", { x: 400, y: 0 }, { width: 600, height: 300 }),
new Widget("metric", { x: 0, y: 300 }, { width: 200, height: 150 }),
],
"secret-api-key-123",
);
// 복제 후 커스터마이징
const myDashboard = defaultDashboard.clone();
myDashboard.theme = "light";
myDashboard.widgets[0].position = { x: 100, y: 100 };
// 원본은 그대로!
console.log(defaultDashboard.theme); // "dark"
console.log(defaultDashboard.widgets[0].position); // { x: 0, y: 0 }
깊은 복사 덕분에 원본과 복제본이 완전히 독립적이다.
visible: false
Prototype Registry: 프리셋 저장소
자주 쓰는 프로토타입을 레지스트리에 등록해두면 편하다.
class ConfigRegistry {
private prototypes = new Map<string, DashboardConfig>();
register(name: string, config: DashboardConfig) {
this.prototypes.set(name, config);
}
create(name: string): DashboardConfig {
const prototype = this.prototypes.get(name);
if (!prototype) {
throw new Error(`"${name}" 프리셋을 찾을 수 없습니다`);
}
return prototype.clone();
}
}
// 프리셋 등록
const registry = new ConfigRegistry();
registry.register("analytics", analyticsDashboard);
registry.register("monitoring", monitoringDashboard);
registry.register("admin", adminDashboard);
// 프리셋으로부터 새 설정 생성
const myAnalytics = registry.create("analytics");
myAnalytics.theme = "light"; // 커스터마이징visible: false
JavaScript에서의 Prototype
사실 JavaScript는 이름부터 프로토타입 기반 언어다.
// JavaScript의 프로토타입 체인
const parent = { greet: () => "Hello!" };
const child = Object.create(parent);
child.greet(); // "Hello!" — parent에서 상속받음하지만 이건 디자인 패턴에서의 Prototype과는 약간 다르다.
- JS Prototype: 프로토타입 체인을 통한 상속 메커니즘
- 디자인 패턴 Prototype: 기존 객체를 복제하여 새 객체를 생성하는 패턴
이름은 같지만 목적이 다르다. 디자인 패턴의 Prototype은 복제에 초점이 있다.
visible: false
structuredClone: 모던 JavaScript의 해법
ES2022부터 structuredClone()이라는 내장 함수가 추가되었다.
const original = {
name: "junbeom",
address: { city: "Seoul", country: "Korea" },
hobbies: ["coding", "reading"],
};
const copy = structuredClone(original);
copy.address.city = "Busan";
console.log(original.address.city); // "Seoul" — 깊은 복사!단, structuredClone은 일반 객체에만 작동한다. 클래스 인스턴스의 메서드나 프로토타입 체인은 복사하지 않는다.
그래서 클래스 기반 객체에는 여전히 clone() 메서드를 직접 구현하는 것이 안전하다.
visible: false
언제 사용하면 좋을까?
-
객체 생성 비용이 클 때
- DB에서 데이터를 로드하거나, 파일을 파싱하는 등 무거운 초기화가 필요한 경우
- 복제하면 이 비용을 건너뛸 수 있다
-
비슷한 객체를 많이 만들어야 할 때
- 게임의 적 캐릭터, 대시보드 프리셋, 문서 템플릿 등
-
객체의 내부 구조를 외부에서 알 수 없을 때
- private 필드가 있거나, 서브클래스가 다양한 경우
-
런타임에 동적으로 타입이 결정될 때
new ClassName()처럼 정적으로 결정할 수 없는 경우
visible: false
장단점
| 장점 | 단점 |
|---|---|
| 복잡한 객체를 빠르게 생성 | 순환 참조가 있으면 복제가 까다로움 |
| 구체 클래스에 의존하지 않고 복제 가능 | 깊은 복사 구현이 실수하기 쉬움 |
| 반복적인 초기화 코드 제거 | clone()을 모든 클래스에 구현해야 함 |
visible: false
정리하며
Prototype은 "이미 있는 걸 복사해서 새로 만들자" 라는 단순한 아이디어의 패턴이다.
핵심을 한 줄로 요약하면,
"처음부터 만들지 말고, 비슷한 걸 복사해서 고쳐라"
특히 JavaScript/TypeScript에서는 객체 복사에 대한 함정(얕은 복사, 참조 공유)이 많기 때문에,
clone() 메서드를 명시적으로 구현하는 Prototype 패턴이 예상치 못한 버그를 방지하는 데 도움이 된다.
다음 글에서는 가장 유명하면서도 가장 논란이 많은 Singleton 패턴을 알아보겠다.
visible: false
#Reference
Refactoring Guru - Prototype
refactoring.guru