C++ - 프로토타입 패턴 (Prototype Pattern)
Architecture & Design Pattern 전체 링크
참고
프로토타입 패턴 (Prototype Pattern) - 생성 패턴
- 초기 객체 생성 비용이 많이 드는 경우 사용하는 패턴
- 객체를 생성할 때 기존 객체의 복사를 통해 생성한다.
- 생성할 객체의 유형에 대한 세부 정보를 숨기면서 복잡한 객체를 만들 수 있는 방법을 제공한다.
구현
- Prototype : 복제될 객체의 인터페이스를 정의, 객체를 복사하는 메서드를 제공한다.
- ConcretePrototype : 복사될 때 생성될 새로운 객체의 초기 상태를 가지게 된다.
- 클라이언트는 Concrete Class의 구체적인 내용을 몰라도 객체를 복사할 수 있다.
장점
- 객체를 생성할 때, 기존 객체를 복사하기 때문에 간단하게 객체를 생성한다.
- 클라이언트가 객체 생성에 필요한 정보를 구체적으로 알 필요가 없다.
- 최초 객체 생성 비용이 많은 경우, 복사를 통해 객체를 만드는 것이 효율적이다.
단점
- 객체가 다른 객체들과 복잡한 관계에 있다면, 객체의 복사 기능 구현이 복잡할 수 있다.
생성 비용이 큰 객체
아래와 같이 어떤 클래스는 객체를 생성하는데 3초 정도의 시간이 걸린다.
class SomethingClass
{
public:
SomethingClass(int value) : id(value)
{
cout << "Create Start!" << endl;
for (int i = 1; i <= 3; i++) //
{
cout << "Waiting " << i << " seconds..." << endl;
this_thread::sleep_for(chrono::seconds(1));
}
cout << "Create End." << endl;
}
void print() const { cout << "SomethingClass " << id << endl; }
void setID(int value) { id = value; }
private:
int id;
};
이런 객체가 많이 필요하다면, 매번 객체를 생성하는 것은 매우 비효율적인 작업이 될 수 있다.
프로토타입 패턴 적용
프로토타입 패턴은 복제될 객체의 인터페이스를 구현하도록 해서, 객체를 복사하는 메서드를 제공한다.
클라이언트는 구체적인 클래스를 알지 못해도 객체를 쉽게 복사할 수 있다.
단, 프로토타입 패턴은 이미 존재하는 객체를 복사하는 것이지, 객체를 생성하는 비용을 줄이는 것은 아니다.
프로토타입 인터페이스는 아래와 같이 정의할 수 있다.
여기서 깊은 복사(Deep Copy)가 잘 이루어지는지 확인하기 위해 setID를 추가하였다.
class Prototype
{
public:
virtual Prototype* clone() = 0;
virtual void print() = 0;
virtual void setID(int value) = 0;
virtual ~Prototype() {}
};
ConcretePrototype에서는 복사 생성자를 이용해 객체를 복사하였다.
그리고 프로토타입 인터페이스의 clone을 구현해서 클라이언트가 사용하도록 한다.
class ConcretePrototype : public Prototype
{
public:
ConcretePrototype(int value) : id(value)
{
cout << "Create Start!" << endl;
for (int i = 1; i <= 3; i++) //
{
cout << "Waiting " << i << " seconds..." << endl;
this_thread::sleep_for(chrono::seconds(1));
}
cout << "Create End." << endl;
}
ConcretePrototype(ConcretePrototype& other) : id(other.id) { cout << "Copy" << endl; }
Prototype* clone() override { return new ConcretePrototype(*this); }
void print() override { cout << "ConcretePrototype " << id << endl; }
void setID(int value) override { id = value; }
private:
int id;
};
여러 가지 프로토타입 객체 (ex. Monster, NPC, ...) 가 존재할 수 있다.
따라서 여기서는 PrototypeManager를 이용해서 여러 객체를 관리한다.
map을 이용해서 생성 비용이 많이 드는 초기 객체들을 관리하도록 한다.
class PrototypeManager
{
public:
static void registerPrototype(string name, Prototype* prototype) { ptMap[name] = prototype; }
static Prototype* getPrototype(string name) { return ptMap[name]->clone(); }
private:
static map<string, Prototype*> ptMap;
};
int main()
{
PrototypeManager::registerPrototype("Monster", new ConcretePrototype(1));
PrototypeManager::registerPrototype("NPC", new ConcretePrototype(2));
...
초기 객체는 PrototypeManager에 등록하고, 각 객체를 PrototypeManager를 이용해 생성해 보자.
그리고 ID를 변경해서 각 객체의 ID가 고유한 값을 유지하는지 확인해 보자.
int main()
{
PrototypeManager::registerPrototype("Monster", new ConcretePrototype(1));
PrototypeManager::registerPrototype("NPC", new ConcretePrototype(2));
Prototype* prototypeA = PrototypeManager::getPrototype("Monster");
Prototype* prototypeB = PrototypeManager::getPrototype("NPC");
cout << "\nPrint A, B" << endl;
cout << "A : "; prototypeA->print();
cout << "B : "; prototypeB->print();
cout << "\nCreate C, D" << endl;
Prototype* prototypeC = PrototypeManager::getPrototype("Monster");
Prototype* prototypeD = PrototypeManager::getPrototype("NPC");
cout << "\nSet ID" << endl;
prototypeC->setID(100);
prototypeD->setID(200);
cout << "\nPrint A, B, C, D" << endl;
cout << "A : "; prototypeA->print();
cout << "B : "; prototypeB->print();
cout << "C : "; prototypeC->print();
cout << "D : "; prototypeD->print();
return 0;
}
이제 초기에 생성된 객체에 대해서만 비용이 발생하고, 해당 객체의 복사는 즉시 이루어진다.
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <map>
#include <thread>
#include <chrono>
using namespace std;
class Prototype
{
public:
virtual Prototype* clone() = 0;
virtual void print() = 0;
virtual void setID(int value) = 0;
virtual ~Prototype() {}
};
class ConcretePrototype : public Prototype
{
public:
ConcretePrototype(int value) : id(value)
{
cout << "Create Start!" << endl;
for (int i = 1; i <= 3; i++) //
{
cout << "Waiting " << i << " seconds..." << endl;
this_thread::sleep_for(chrono::seconds(1));
}
cout << "Create End." << endl;
}
ConcretePrototype(ConcretePrototype& other) : id(other.id) { cout << "Copy" << endl; }
Prototype* clone() override { return new ConcretePrototype(*this); }
void print() override { cout << "ConcretePrototype " << id << endl; }
void setID(int value) override { id = value; }
private:
int id;
};
class PrototypeManager
{
public:
static void registerPrototype(string name, Prototype* prototype) { ptMap[name] = prototype; }
static Prototype* getPrototype(string name) { return ptMap[name]->clone(); }
private:
static map<string, Prototype*> ptMap;
};
map<string, Prototype*> PrototypeManager::ptMap;
int main()
{
PrototypeManager::registerPrototype("Monster", new ConcretePrototype(1));
PrototypeManager::registerPrototype("NPC", new ConcretePrototype(2));
Prototype* prototypeA = PrototypeManager::getPrototype("Monster");
Prototype* prototypeB = PrototypeManager::getPrototype("NPC");
cout << "\nPrint A, B" << endl;
cout << "A : "; prototypeA->print();
cout << "B : "; prototypeB->print();
cout << "\nCreate C, D" << endl;
Prototype* prototypeC = PrototypeManager::getPrototype("Monster");
Prototype* prototypeD = PrototypeManager::getPrototype("NPC");
cout << "\nSet ID" << endl;
prototypeC->setID(100);
prototypeD->setID(200);
cout << "\nPrint A, B, C, D" << endl;
cout << "A : "; prototypeA->print();
cout << "B : "; prototypeB->print();
cout << "C : "; prototypeC->print();
cout << "D : "; prototypeD->print();
return 0;
}
ClassDiagram.md
```mermaid
classDiagram
class Prototype {
clone()*
}
class ConcretePrototypeA {
clone()
}
class ConcretePrototypeB {
clone()
}
<<interface>> Prototype
Client --> Prototype
Prototype <|.. ConcretePrototypeA
Prototype <|.. ConcretePrototypeB
```