Prototype
Prototype은 코드를 클래스에 의존시키지 않고 기존 객체를 복사할 수 있게 하는 생성 디자인 패턴입니다.

Intent
Prototype 패턴은 복제를 지원하는 객체를 프로토타입이라고 합니다. 객체에 수십 개의 필드와 수백 개의 구성이 있을 때, 서브클래싱 대신 복제가 대안이 될 수 있습니다.
Problem
객체가 있고 그것의 정확한 복사본을 만들고 싶다면 어떻게 해야 할까요?

먼저 같은 클래스의 새 객체를 생성해야 합니다. 그런 다음 원본 객체의 모든 필드를 순회하며 값을 새 객체로 복사해야 합니다.
하지만 문제가 있습니다:

- private 필드: 외부에서 모든 필드를 복사할 수 없습니다. 일부 필드는 private일 수 있습니다.
- 클래스 의존성: 복사본을 만들려면 객체의 클래스를 알아야 하므로 코드가 해당 클래스에 의존하게 됩니다.
- 인터페이스만 알 때: 때로는 객체가 따르는 인터페이스만 알고 구체적인 클래스는 모를 수 있습니다.
Solution

Prototype 패턴은 복제 프로세스를 복제되는 실제 객체에 위임합니다. 패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언합니다. 이 인터페이스를 통해 코드를 해당 객체의 클래스에 결합하지 않고 객체를 복제할 수 있습니다.
clone 메서드의 구현은 모든 클래스에서 매우 유사합니다. 메서드는 현재 클래스의 객체를 생성하고 이전 객체의 모든 필드 값을 새 객체로 전달합니다. 대부분의 프로그래밍 언어에서 객체는 같은 클래스에 속한 다른 객체의 private 필드에 접근할 수 있으므로 private 필드도 복사할 수 있습니다.
Real-World Analogy
실생활에서 프로토타입은 제품의 대량 생산 전에 다양한 테스트를 수행하는 데 사용됩니다. 하지만 이 경우 프로토타입은 실제 생산에 참여하지 않고 수동적인 역할만 합니다.
산업용 프로토타입은 실제로 스스로를 복제하지 않기 때문에, 패턴에 더 가까운 비유는 세포 분열(생물학)입니다. 유사 분열 후에 한 쌍의 동일한 세포가 형성됩니다. 원래 세포가 프로토타입 역할을 하며 복사본을 만드는 데 적극적인 역할을 합니다.
Structure
기본 구현


| 구성 요소 | 역할 |
|---|---|
| Prototype | 복제 메서드를 선언하는 인터페이스. 대부분 단일 clone 메서드 |
| Concrete Prototype | 복제 메서드를 구현. 원본 객체의 데이터를 복제본으로 복사하고 순환 참조 같은 엣지 케이스 처리 |
| Client | 프로토타입 인터페이스를 따르는 모든 객체의 복사본을 생성 |
프로토타입 레지스트리 구현


프로토타입 레지스트리는 자주 사용되는 프로토타입에 쉽게 접근할 수 있게 합니다. 이름에서 프로토타입으로의 매핑을 저장하거나 강력한 검색 기능을 제공합니다.
Pseudocode
도형 복제 예시:

// 프로토타입 인터페이스interface Cloneable { clone(): Cloneable;}
// 기본 도형 추상 클래스abstract class Shape implements Cloneable { x: number; y: number; color: string;
constructor(); constructor(source: Shape); constructor(source?: Shape) { if (source) { this.x = source.x; this.y = source.y; this.color = source.color; } }
abstract clone(): Shape;}
// 사각형 구체 프로토타입class Rectangle extends Shape { width: number; height: number;
constructor(); constructor(source: Rectangle); constructor(source?: Rectangle) { super(source); if (source) { this.width = source.width; this.height = source.height; } }
clone(): Shape { return new Rectangle(this); }}
// 원 구체 프로토타입class Circle extends Shape { radius: number;
constructor(); constructor(source: Circle); constructor(source?: Circle) { super(source); if (source) { this.radius = source.radius; } }
clone(): Shape { return new Circle(this); }}
// 클라이언트 코드class Application { shapes: Shape[] = [];
constructor() { // 원 생성 및 복제 const circle = new Circle(); circle.x = 10; circle.y = 10; circle.radius = 20; circle.color = "red"; this.shapes.push(circle);
// 원 복제 const anotherCircle = circle.clone() as Circle; this.shapes.push(anotherCircle);
// 사각형 생성 및 복제 const rectangle = new Rectangle(); rectangle.width = 10; rectangle.height = 20; rectangle.color = "blue"; this.shapes.push(rectangle);
const anotherRectangle = rectangle.clone(); this.shapes.push(anotherRectangle); }
businessLogic(): void { // 모든 도형의 복사본 생성 const shapesCopy: Shape[] = [];
for (const shape of this.shapes) { // 구체적인 클래스를 몰라도 복제 가능 shapesCopy.push(shape.clone()); }
console.log(`Original: ${this.shapes.length} shapes`); console.log(`Copy: ${shapesCopy.length} shapes`); }}프로토타입 레지스트리 예시
class ShapeCache { private static cache: Map<string, Shape> = new Map();
static getShape(id: string): Shape { const shape = this.cache.get(id); if (shape) { return shape.clone(); } throw new Error(`Shape ${id} not found in cache`); }
static loadCache(): void { const circle = new Circle(); circle.x = 0; circle.y = 0; circle.radius = 5; circle.color = "red"; this.cache.set("red-circle", circle);
const rectangle = new Rectangle(); rectangle.x = 0; rectangle.y = 0; rectangle.width = 10; rectangle.height = 5; rectangle.color = "blue"; this.cache.set("blue-rectangle", rectangle); }}
// 사용ShapeCache.loadCache();const cachedCircle = ShapeCache.getShape("red-circle");const cachedRectangle = ShapeCache.getShape("blue-rectangle");Applicability
다음의 경우에 패턴을 사용합니다:
클래스 의존성을 피하고 싶을 때
복사해야 하는 객체의 구체적인 클래스에 코드가 의존하면 안 될 때 사용합니다. 예를 들어, 서드파티 코드가 인터페이스를 통해 객체를 전달할 때 구체적인 클래스를 알 수 없습니다.
서브클래스 수를 줄이고 싶을 때
객체 초기화 방식만 다른 서브클래스의 수를 줄이고 싶을 때 사용합니다. 다양한 구성으로 미리 빌드된 프로토타입 세트를 사용하면 됩니다.
How to Implement
-
프로토타입 인터페이스 생성:
clone메서드를 선언합니다. 기존 클래스 계층이 있다면 해당 클래스에 메서드를 추가합니다. -
대체 생성자 정의: 프로토타입 클래스에 같은 클래스의 객체를 인자로 받는 대체 생성자를 정의합니다. 이 생성자는 전달된 객체에서 모든 필드 값을 복사해야 합니다.
-
clone 메서드 구현: 보통 한 줄로 구성됩니다: 프로토타입 버전의 생성자로
new연산자 실행. -
프로토타입 레지스트리 생성 (선택): 자주 사용되는 프로토타입 카탈로그를 저장하는 중앙 레지스트리를 만듭니다.
Pros and Cons
장점
| 장점 | 설명 |
|---|---|
| 클래스 비의존 | 구체적인 클래스에 결합하지 않고 객체를 복제할 수 있습니다 |
| 초기화 코드 제거 | 미리 빌드된 프로토타입을 복제하여 반복적인 초기화 코드를 제거할 수 있습니다 |
| 복잡한 객체 생성 | 복잡한 객체를 더 편리하게 생성할 수 있습니다 |
| 상속 대안 | 구성 프리셋을 다룰 때 상속의 대안이 됩니다 |
단점
| 단점 | 설명 |
|---|---|
| 순환 참조 | 순환 참조가 있는 복잡한 객체를 복제하는 것은 매우 까다로울 수 있습니다 |
Relations with Other Patterns
| 패턴 | 관계 |
|---|---|
| Factory Method | Factory Method보다 유연하지만 초기화가 더 복잡. Factory Method는 상속 기반, Prototype은 위임 기반 |
| Abstract Factory | Abstract Factory 클래스는 종종 Factory Method 세트를 기반으로 하지만 Prototype으로 구성할 수도 있음 |
| Command | 명령을 히스토리에 저장할 때 Prototype으로 복사본 보관 |
| Composite, Decorator | 복잡한 구조를 재구성하는 대신 Prototype으로 복제 가능 |
| Memento | 단순한 객체의 경우 Prototype이 Memento의 더 간단한 대안이 될 수 있음 |
| Singleton | Abstract Factory, Builder, Prototype 모두 Singleton으로 구현될 수 있음 |