[모던 C++ 디자인 패턴] 책을 바탕으로 공부하는 내용을 정리한 내용이다.
Prototype pattern
처음부터 모두 새로 만드는 방식(이 경우 팩토리나 빌더 패턴이 유용)은 어렵다. 대신, 이미 존재하는 객체를 이용해 복제해 사용하는 방식은 처음부터 새로 만들지 않고 쉽게 생성할 수 있다. 아니면 일부를 수정하거나 확장할 수도 있을 것이다.
이러한 아이디어를 갖고 프로토타입 패턴이 나오게 된다. 프로토타입 패턴을 이용하면 어떤 모델 객체도 복제할 수 있고, 복제본을 커스터마이징해 사용할 수도 있다.
가장 까다로운 부분이라면, 복제를 구현하는 부분이다.
객체 생성
생성자를 통해 객체를 생성하는 것보다 이미 잘 설정된 객체를 복제하는 것이 보다 쉬운 방법이다. 빌더 패턴을 이용해 복잡한 생성 과정을 단순화시킨 경우라면 더욱 복제가 유용할 수 있다.
다음과 같은 예시의 둘 이상의 객체가 존재한다면, 중복으로 초기화하는 것보다는 반복된 설정을 피할 수 있는 복제가 더 효과적일 것이다.
Contact john{ "John Doe", Address{"123 East Dr", "London", 10} };
Contact jane{ "Jane Doe", Address{"123 East Dr", "London", 11} };
아래에는 복제를 하는 여러 방안을 소개한다.
평범한 중복 처리
복제 대상 객체가 모든 항목이 값으로만 되어 있다면 복제하는데 문제 될 것이 없다. 하지만, 실제로는 이런 경우가 드물다. 보통은 내부 객체 포인터를 갖는 경우들이 있을 것이다. 프로토타입을 구하기 위해서는 깊은 복사를 수행해야 한다.
(코드 생략)
복제 생성자를 통한 중복 처리
중복을 피하면서 완전한 복사를 하기 위해서는, 복사 생성자를 구현해 내부 구성 요소를 모두 적합하게 다루도록 정의하는 방법이 있다.
struct Contact {
// 복사 생성자 정의
Contact(const Contact &other) : sName(other.sName) {
pAddress = new Address(other.pAddress->street,
other.pAddress->city,
other.pAddress->suite);
}
std::string sName;
Address *pAddress;
};
하지만, 위의 코드는 범용적이지 못하다. 주소의 항목이 바뀌기라도 한다면 동일한 문제에 직면하게 될 것이고, 그때마다 수정이 필요할 것이다. 따라서 Address 에도 마찬가지로 복사 생성자를 정의하는 방법이 있을 것이다.
struct Address {
// 생성자 정의
Address(const std::string &street, const std::string &city, const int suite) :
street(street), city(city), suite(suite) {
}
std::string street;
std::string city;
int suite;
};
struct Contact {
// 복사 생성자 정의
Contact(const Contact &other) : sName(other.sName), pAddress(new Address(*other.pAddress)) {
}
std::string sName;
Address *pAddress;
};
직렬화(Serialization)
다른 프로그래밍 언어 설계자도 같은 문제에 부딪혔다. 객체 복제를 위해 그 구성 요소 그래프 전체에 걸쳐 각 요소마다 명시적 복제 연산을 일일이 정의하는 것은 불편하다. 이를 해결하기 위해 어떤 클래스든 쉽게 직렬화될 수 있어야 한다는 결론이 도달하게 된다. 별도 변환 없이 직렬화가 가능하다면, 복제가 쉬워진다.
직렬 화한다는 것은 어떤 객체 데이터를 비트의 나열로 만들어 파일에 저장하거나 네트워크로 전송할 수 있게 하는 것을 말한다.
하지만, C++에서는 직렬화를 언어 차원에서 지원하지 않는다. 다른 프로그래밍 언어와 달리 컴파일된 바이너리에 풍부한 메타 데이터도 없고, 리플랙션 기능도 없다. 따라서 복사 및 대입 연산을 직접 정의한 것처럼 직렬화도 일일이 정의해야 한다.
대표적으로 Boost 라이브러리의 Serialization이 있다. 모던 C++에 추가되지는 않은 듯하다. 아래의 코드는 Boost의 Serialization을 이요한 예제다.
struct Address {
std::string street;
std::string city;
int suite;
private:
friend class boost::serialization::access;
template<class Ar> void serializate(Ar &ar, const unsigned int version) {
ar &street;
ar &city;
ar &suite;
}
};
struct Contact {
std::string name;
Address *address = nullptr;
private:
friend class boost::serialization::access;
template<class Ar> void serializate(Ar &ar, const unsigned int version) {
ar &name;
ar &address; // *가 없다
}
};
썩 괜찮은 코드로 보이지는 않는다. 최종적으로 & 연산자로 지정한 대로 Address를 구성하는 모든 항목이 객체를 저장하는 위치에 비트열로 기록된다. 위의 코드는 저장과 복구 모두에서 사용되는 멤버 함수다. 저장과 복구 각각에 대해 사용되는 멤버 함수를 별도로 지정할 수도 있지만, 프로토타입 예에서는 굳이 찾아볼 필요 없다.
Contact의 address를 직렬 화할 때는 포인터 역참조 없이 바로 사용하고 있다. 이것은 Boost 라이브러리가 자동으로 대응하기 때문이다.
프로토타입 팩토리
자주 복제해 사용할 기본 객체들은 어디에 저장해 두어야 할까? 본점/지점 정보가 나눠져 있는데, 임직원 정보를 생성할 때 기존 정보를 복제해 사용한다고 해보자. 가장 쉬운 방법은 전역 변수로 두는 방법일 것이다.
좀 더 직관적이고 나은 방법은 프로토타입을 별도 저장할 클래스를 두고, 사용자가 원할 때 목적에 맞는 복제본을 만들어 제공하는 것이다. 이렇게 할 경우 전역 변수로 관리할 때보다 더 융통성 있는 일들이 가능해진다.
struct EmployeeFactory {
static Contact main;
static Contact aux;
static Contact *NewMainOfficeEmployee(std::string name, int suite) {
return NewEmployee(name, suite, main); // main prototype
}
static Contact *NewAuxOfficeEmployee(std::string name, int suite) {
return NewEmployee(name, suite, aux); // aux prototype
}
private:
static Contact *NewEmployee(std::string name, int suite, Contact &proto) {
Contact result = proto;
result->name = name;
result->address->suite = suite;
return result;
}
};
요약
객체의 깊은 복사를 수행하되, 매번 전체 초기화를 하는 대신에 미리 부분적으로 만들어진 객체를 복제해 수정해서 사용할 수 있도록 한다.
프로토 타입은 아래의 방법으로 구현 가능하다.
- 깊은 복사를 수행하는 코드(복사 생성자 / 복사 대입 연산자 / 별도 함수 등)를 작성한다.
- 직렬화/역직렬화 기능을 통해 복사를 한다.
두 방법 모두 ReShaper나 CLion과 같은 코드 생성 도구를 이용하면 도움을 받을 수 있으나, 다소의 수작업은 반드시 필요하다.
'study > design pattern' 카테고리의 다른 글
[디자인패턴][구조패턴] 어댑터 Adapter - C++ (0) | 2022.05.11 |
---|---|
[디자인패턴][생성패턴] 싱글턴 Singleton - C++ (0) | 2022.04.30 |
[디자인패턴][생성패턴] 팩터리 Factory - C++ (0) | 2022.03.12 |
[디자인패턴][생성패턴] 빌더 Builder - C++ (0) | 2022.03.09 |
[디자인패턴] 공부를 시작하면서 (개요) (0) | 2022.03.08 |