Outdated/Column

[Design Pattern] 프로토타입 패턴(prototype pattern)

해달 2020. 3. 22. 08:00

목차

  1. 프로토타입 패턴이란?

  2. 복사 연산 이용하기

  3. 직렬화

  4. 팩토리 패턴과의 결합

  5. 정리하며

  6. 참고자료



1. 프로토타입 패턴이란?

객체를 생성할 때 종종 다른 객체의 값을 복사하여 사용할 때가 있다. 특히, 객체의 멤버 중 공통된 부분이 있다면 이것을 일일이 초기화 하기 보다는 어떤 객체를 잘 설정해놓고 복사하는 편이 효율적이다. 프로토타입 패턴(prototype pattern)은 그 이름에서 유추할 수 있듯 어떤 객체에 대한 프로토타입을 만들어 놓고 그것을 복사하는 패턴이다. C++로 프로토타입을 구현하는 방법에는 2가지가 있다. 하나는 복사 연산을 이용하는 것이고, 다른 하나는 직렬화를 사용하는 것이다.

2. 복사 연산 이용하기

복사 연산을 이용한 프로토타입 패턴 구현은 아래와 같이 될 것이다.


class Undergraduate

{

    int             age;

    std::string     name;

    std::string     university;

 

public:

    // 여러 코드들...

    Undergraduate(const Undergraduate& other)

        : age{ other.age }, name{ other.name },

        university{ other.university }

    {

 

    }

 

    Undergraduate& operator=(const Undergraduate& other)

    {

        if (this != &other)

        {

            Undergraduate temp(other);

            *this = std::move(temp);

        }

 

        return *this;

    }

};

 

// 복사 생성자를 이용한 프로토타입은 아래와 같이 쓴다.

// 어떤 학생들이 모두 한국대학교를 다닌다고 가정한다면

Undergraduate hankukPrototype(0, "", "한국대학교");  // 공통된 멤버를 채우고

Undergraduate chulsu(hankukPrototype);              // 그 멤버를 이용해 객체를 생성하여

chulsu.setName("철수");                             // 남은 부분을 수정한다.

chulsu.setAge(23);

복사 연산을 이용한 구현


공통된 멤버를 초기화 한 프로토타입 객체를 만들고, 그 객체를 복사해 새로운 객체를 만든 다음 공통되지 않는 멤버를 수정하는 것이다.


하지만 이 방법은 단점이 있는데 복사 연산을 구현해본 분들은 아시겠지만, 올바르게 복사 연산을 구현하는 것이 여간 까다롭다. 작성하는 데 손이 많이 갈 뿐더러 객체의 얕은 복사가 일어나지 않도록 주의해야 한다. 다시 말해 멤버 중 포인터, 레퍼런스, 스마트 포인터 등이 있다면 적절히 코드를 작성해줘야 한다. 또, 수정이 필요한 데이터를 수정하지 않는 오류를 범할 수도 있다.


복잡도를 낮추려면 복사 대입 연산자나 복사 생성자 중 하나만 제공하거나, ICloneable 인터페이스를 둘 수 있다.


template <typename T>

class ICloneable

{

public:

    virtual T Clone() = 0;

};

ICloneable

3. 직렬화

직렬화(serialization)는 데이터를 파일에 저장하거나, 다른 프로세스에 전달하기 위해 데이터를 바이트 스트림(byte stream)으로 변환하는 프로세스를 말한다. 특히 데이터 복원이 쉬워 네트워크로 전송할 때 많이 사용된다. 이러한 특징으로 어떤 객체에 직렬화 기능을 제공하면 객체 복사를 쉽게 할 수 있다. 바이트 스트림을 데이터로 복원하는 작업역직렬화(deserialization)라고 한다. 


하지만 아쉽게도 C#, Java 등과 다르게 C++은 언어 차원에서 직렬화를 지원하지 않는다.  필요하다면 Boost.Serialization 라이브러리를 사용할 수 있다.

4. 팩토리 패턴과의 결합

여러 개의 프로토타입이 필요하다면 팩토리 패턴과 같이 활용할 수 있다. 팩토리 패턴과 결합하면 미완성된 객체 복사본의 존재를 원천적으로 막을 수 있다.


 

class Undergraduate;

 

class UndergraduateFactory

{

    static std::string hankuk;

    static std::string hankok;

 

    static std::unique_ptr<Undergraduate> createUndergraduate(

        const std::string& name, int age, std::string university)

    {

        return std::make_unique(age, name, university)

    }

public:

    static std::unique_ptr<Undergraduate> CreateNewHankuk(const std::string& name, int age)

    {

        return createUndergraduate(name, age, hankuk);

    }

 

    static std::unique_ptr<Undergraduate> CreateNewHankok(const std::string& name, int age)

    {

        return createUndergraduate(name, age, hankok);

    }

};

 

static UndergraduateFactory::hankuk = "한국대학교";

static UndergraduateFactory::hankok = "한곡대학교";

팩토리 패턴과 결합한 프로토타입 패턴

5. 정리하며

프로토타입 패턴을 사용하면 공통된 멤버에 관한 값을 쉽게 전달할 수 있다. 객체의 깊은 복사를 하기 때문에 원본 객체에 대한 걱정은 하지 않아도 된다. C++에서는 복사 연산을 올바르게 수행하거나 직렬화를 이용하여 구현할 수 있다. 

6. 참고자료