Skip to content
2글자 이상 입력하세요
creational으로

Prototype

11 min 읽기
creational-patternsprototypecloningdesign-patterns

Prototype

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

Prototype pattern

Intent

Prototype 패턴은 복제를 지원하는 객체를 프로토타입이라고 합니다. 객체에 수십 개의 필드와 수백 개의 구성이 있을 때, 서브클래싱 대신 복제가 대안이 될 수 있습니다.

Problem

객체가 있고 그것의 정확한 복사본을 만들고 싶다면 어떻게 해야 할까요?

외부에서 복사하기

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

하지만 문제가 있습니다:

private 필드 문제

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

Solution

복제 위임

Prototype 패턴은 복제 프로세스를 복제되는 실제 객체에 위임합니다. 패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언합니다. 이 인터페이스를 통해 코드를 해당 객체의 클래스에 결합하지 않고 객체를 복제할 수 있습니다.

clone 메서드의 구현은 모든 클래스에서 매우 유사합니다. 메서드는 현재 클래스의 객체를 생성하고 이전 객체의 모든 필드 값을 새 객체로 전달합니다. 대부분의 프로그래밍 언어에서 객체는 같은 클래스에 속한 다른 객체의 private 필드에 접근할 수 있으므로 private 필드도 복사할 수 있습니다.

Real-World Analogy

실생활에서 프로토타입은 제품의 대량 생산 전에 다양한 테스트를 수행하는 데 사용됩니다. 하지만 이 경우 프로토타입은 실제 생산에 참여하지 않고 수동적인 역할만 합니다.

산업용 프로토타입은 실제로 스스로를 복제하지 않기 때문에, 패턴에 더 가까운 비유는 세포 분열(생물학)입니다. 유사 분열 후에 한 쌍의 동일한 세포가 형성됩니다. 원래 세포가 프로토타입 역할을 하며 복사본을 만드는 데 적극적인 역할을 합니다.

Structure

기본 구현

Prototype 구조

Prototype 구조 상세

구성 요소역할
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

  1. 프로토타입 인터페이스 생성: clone 메서드를 선언합니다. 기존 클래스 계층이 있다면 해당 클래스에 메서드를 추가합니다.

  2. 대체 생성자 정의: 프로토타입 클래스에 같은 클래스의 객체를 인자로 받는 대체 생성자를 정의합니다. 이 생성자는 전달된 객체에서 모든 필드 값을 복사해야 합니다.

  3. clone 메서드 구현: 보통 한 줄로 구성됩니다: 프로토타입 버전의 생성자로 new 연산자 실행.

  4. 프로토타입 레지스트리 생성 (선택): 자주 사용되는 프로토타입 카탈로그를 저장하는 중앙 레지스트리를 만듭니다.

Pros and Cons

장점

장점설명
클래스 비의존구체적인 클래스에 결합하지 않고 객체를 복제할 수 있습니다
초기화 코드 제거미리 빌드된 프로토타입을 복제하여 반복적인 초기화 코드를 제거할 수 있습니다
복잡한 객체 생성복잡한 객체를 더 편리하게 생성할 수 있습니다
상속 대안구성 프리셋을 다룰 때 상속의 대안이 됩니다

단점

단점설명
순환 참조순환 참조가 있는 복잡한 객체를 복제하는 것은 매우 까다로울 수 있습니다

Relations with Other Patterns

패턴관계
Factory MethodFactory Method보다 유연하지만 초기화가 더 복잡. Factory Method는 상속 기반, Prototype은 위임 기반
Abstract FactoryAbstract Factory 클래스는 종종 Factory Method 세트를 기반으로 하지만 Prototype으로 구성할 수도 있음
Command명령을 히스토리에 저장할 때 Prototype으로 복사본 보관
Composite, Decorator복잡한 구조를 재구성하는 대신 Prototype으로 복제 가능
Memento단순한 객체의 경우 Prototype이 Memento의 더 간단한 대안이 될 수 있음
SingletonAbstract Factory, Builder, Prototype 모두 Singleton으로 구현될 수 있음

출처: refactoring.guru - Prototype


이전 글

Local Test Post

다음 글

Singleton