[Design Pattern] 프로토타입(Prototype) 패턴
프로토타입(Prototype)이란?
프로토타입 패턴은 기존 객체(프로토타입)를 복사하여 새로운 객체를 생성하는 디자인 패턴입니다.
new
키워드를 사용하여 객체를 직접 생성하는 대신, 기존 객체를 복제함으로써 객체 생성 과정을 간소화하고 성능을 향상시킬 수 있습니다.
특히, 객체 생성 과정이 복잡하거나 비용이 많이 드는 경우, 또는 생성할 객체의 구체적인 클래스를 런타임에 결정해야 하는 경우에 유용합니다.
구성요소
- Prototype (프로토타입): 복제할 객체의 인터페이스 또는 추상 클래스를 정의합니다.
clone()
과 같은 복제 메서드를 선언합니다. - ConcretePrototype (구체적인 프로토타입): 프로토타입 인터페이스를 구현하는 구체적인 클래스입니다. 자신을 복제하는 clone() 메서드를 구현합니다.
- Client (클라이언트): 프로토타입을 사용하여 새로운 객체를 생성하는 클래스입니다.
프로토타입(Prototype) 패턴의 동작 방식
- 클라이언트는 프로토타입 객체를 가지고 있습니다.
- 클라이언트가 새로운 객체를 생성해야 할 때, 프로토타입 객체의
clone()
메서드를 호출합니다. clone()
메서드는 자신(프로토타입)의 복사본을 생성하여 반환합니다. 이 때, 얕은 복사(shallow copy) 또는 깊은 복사(deep copy)를 사용할 수 있습니다.- 클라이언트는 복제된 객체를 사용하여 필요한 작업을 수행합니다.
프로토타입(Prototype) 패턴의 장단점
장점
- 객체 생성 과정 단순화: 복잡한 객체 생성 과정을 숨기고, 간단하게 복제를 통해 객체를 생성할 수 있습니다.
- 성능 향상:
new
연산자를 반복적으로 사용하는 것보다 기존 객체를 복제하는 것이 더 효율적일 수 있습니다. 특히, 객체 생성 비용이 높은 경우 성능 향상 효과가 큽니다. - 런타임 객체 타입 결정: 런타임에 생성할 객체의 종류를 결정할 수 있습니다.
- 클래스 수정 없이 객체 추가 가능: 새로운 객체 타입을 추가하더라도 기존 코드를 수정할 필요가 없습니다. 새로운 프로토타입 클래스를 추가하고 클라이언트에서 해당 프로토타입을 사용하도록 변경하면 됩니다.
단점
- 복잡한 객체의 경우 복제 구현이 어려울 수 있음: 객체의 구조가 복잡한 경우, 깊은 복사를 구현하는 것이 어려울 수 있습니다. 특히, 순환 참조가 있는 경우 복제 로직이 더욱 복잡해집니다.
- 클래스 계층 구조가 복잡해질 수 있음: 프로토타입이 많아지면 클래스 계층 구조가 복잡해질 수 있습니다.
구현
#include <iostream>
#include <string>
class Shape { // 프로토타입 인터페이스
public:
virtual ~Shape() {}
virtual Shape* clone() = 0;
virtual void draw() = 0;
};
class Rectangle : public Shape { // 구체적인 프로토타입
private:
int width_;
int height_;
public:
Rectangle(int width, int height) : width_(width), height_(height) {}
Rectangle(const Rectangle& other) : width_(other.width_), height_(other.height_) { // 복사 생성자 (깊은 복사)
std::cout << "Rectangle copy constructor called" << std::endl;
}
~Rectangle(){
std::cout << "Rectangle destructor called" << std::endl;
}
Shape* clone() override {
return new Rectangle(*this); // 복사 생성자를 이용한 복제 (깊은 복사)
}
void draw() override {
std::cout << "Drawing a rectangle with width " << width_ << " and height " << height_ << std::endl;
}
};
class Circle : public Shape { // 또 다른 구체적인 프로토타입
private:
int radius_;
public:
Circle(int radius) : radius_(radius) {}
Circle(const Circle& other) : radius_(other.radius_) { // 복사 생성자 (깊은 복사)
std::cout << "Circle copy constructor called" << std::endl;
}
~Circle(){
std::cout << "Circle destructor called" << std::endl;
}
Shape* clone() override {
return new Circle(*this); // 복사 생성자를 이용한 복제 (깊은 복사)
}
void draw() override {
std::cout << "Drawing a circle with radius " << radius_ << std::endl;
}
};
int main() {
Rectangle* rect = new Rectangle(10, 20);
Shape* rectClone = rect->clone();
rectClone->draw(); // Drawing a rectangle with width 10 and height 20
delete rect;
delete rectClone;
std::cout << std::endl;
Circle* circle = new Circle(5);
Shape* circleClone = circle->clone();
circleClone->draw(); // Drawing a circle with radius 5
delete circle;
delete circleClone;
return 0;
}