Programming/C++

C++ 프로토 타입 패턴

_SYPark 2021. 5. 8. 15:06
728x90

객체를 생성하는 방법에는 이미 생성된 객체를 복제해서 생성하는 게 유용할 때도 있습니다.

우리가 사용하는 파워포인트에 보면 각 도형이나 텍스트 상자 등이 있습니다. 이 객체들을 드래그해서 화면에 끌어오게 되면 생성이 됩니다. 이 구조를 보면 아래와 같이 볼 수 있습니다.

AddObject 함수에서는 상단 메뉴에서 선택한 오브젝트에 따라서 각 도형들을 생성해주고 Draw 하게 됩니다.

class PowerPointApplication {
    void AddObject(Object obj) {
        swtich(obj.getType()) {
        case Text: {
            pObj = new Text();
            break;
        } ...
    	}
        pObj->draw();
    }
};

이제 여기서 객체를 직접 생성하는 부분은 수정이 일어날 경우 해당 부분을 다 고쳐야하는 번거로움이 있기 때문에 팩토리 메서드 패턴을 이용해서 생성해주는 부분을 관리할 수 있습니다.

class PowerPointApplication {
    void AddObject(Object obj) {
        swtich(obj.getType()) {
        case Text: {
            pObj = textCreator.create();
            break;
        } ...
    	}
        pObj->draw();
    }
};

하지만 가장 문제가 되는 부분은 오브젝트 종류가 많아지면 그에 따른 switch에 case가 증가하고 수정을 해줘야 한다는 단점이 있습니다. 이런 단점을 해결하기 위해서는 switch를 통해 객체를 구분하는 로직을 없애야 합니다. switch를 통해 선택된 오브젝트의 타입에 따라 객체를 다시 생성하는 것이 아니라 기존의 객체 자체를 복제해서 생성을 하면 구분 자체를 해줄 필요가 없어집니다.

class Object {
    virtual void Draw() = 0;
    virtual Object* Clone() = 0;
};
class Circle : public Object {
    void Draw() {}
    Object* Clone() { return new Circle(*this); }
};

class PowerPointApplication {
    void AddObject(Object obj) {
        pObj = obj->Clone();
        pObj->draw();
    }
};

각 Object 객체들은 모두 Clone 멤버함수를 가지고 있는데 이 함수는 자기 자신을 복제해서 반환해주게 됩니다. 이렇게 되면 사용자가 선택한 Object를 복제해서 사용하기 때문에 따로 구분해서 생성해줄 필요가 없습니다. 물론 상단 메뉴는 각각의 오브젝트를 미리 생성해서 관리해주고 있어야 합니다. 위와 같이 복제를 통해 객체를 생성하는 방식을 Prototype 패턴이라 합니다. Prototype 패턴을 통해 새로운 오브젝트 요소들을 동적으로 관리할 수 있게 하기 위해서는 Composite 패턴을 통해 관리해야 합니다.

 

Composite 패턴 사용을 위해서 Object를 상속받는 ObjectComposite 클래스를 생성합니다. ObjectComposite는 모든 Object 객체들을 구성요소로 가지게 됩니다. 만약 Text와 Rectangle을 그룹화하여 새로운 Object를 만들어 사용하고 싶다면 ObjectComposite를 생성하고 이를 상단 메뉴에 등록하는 방법으로 관리할 수 있습니다. 그리고 등록한 오브젝트를 생성한다면 똑같이 clone을 통해 생성을 해주면 됩니다.

class Object {
    virtual void Draw() = 0;
    virtual Object* Clone() = 0;
};
class Circle : public Object {
    void Draw() {}
    Object* Clone() { return new Circle(*this); }
};
class ObjectComposite : public Object {
    void Draw() {}
    Object* Clone() {
    	ObjectComposite* pCom = new ObjectComposite(*this);
        list<Object*>::iterator iter1;
        list<Object*>::iterator iter2;
        iter2 = pCom->lComponent.begin();
        for(iter1 = lComponent.begin(); iter1 != lComponent.end(); iter1++) {
        	Object* pObj = (*iter1)->Clone();
            *iter2 = pObj;
            iter2++;
        }
        return pCom;
    }
    private:
	list<Object*> lComponent;
};

class PowerPointApplication {
    void AddObject(Object* pSelectedObject) {
    	Object* pObj = pSelectedObject->Clone();
        pObj->Draw();
    }
};

Clone에서는 간단히 복사 생성자를 통해서 구현되고 있는데 기본적으로는 얕은 복사가 이루어지기 때문에 실제 내용물까지 모두 복사가 필요하다면 클래스마다 복사 생성자를 따로 구현해서 깊은 복사가 이루어지게 할 수 있습니다. ObjectComposite은 여러 여러 오브젝트들을 리스트로 관리하기 때문에 복제를 위해서는 각 리스트의 Object를 Clone 한 객체를 다시 list에 넣은 ObjectComposite을 반환합니다.

 

Prototype을 통해 구현을 하게될 때 패턴을 적용할 대상이 일정한지 혹은 동적으로 추가 삭제되는지에 대한 상황에 따라 구현을 해줘야 합니다. 동적인 대상이라면 상단 메뉴 같은 객체에 Object들을 추가, 삭제, 수정하는 부분이 있어야 합니다. 이런 식으로 동적으로 관리를 위한 객체를 Prototype Manager라고 합니다. Prototype Manager는 멤버 접근을 위해서 Object들을 map형태로 관리하게 됩니다.

class MenuBar {
    MenuBar() {
        Object* pObj = new Text;
        items["Text"] = pObj;
        ...;
    } // 정해져 있는 오브젝트들
    void RegisterObject(Object* object, String sKey) {
        item[sKey] = object;
    } 
    ...
    // 동적으로 관리하기 위한 함수들
private:
    map<String, Object*> items;
}

Clone을 통해 원본 객체의 값과 동일한 값을 가지는 형태로 복제하는 것이 일반적이지만 복제할 때 다른 값을 가지게 하고 싶을 수도 있습니다. 이럴 때는 Clone을 통해 원본 값을 복사 후 다시 값을 바꾸는 Setter 함수 등을 통해 바꿔주는 것이 바람직합니다.

 

그동안 설명한 것처럼 ProtoType 패턴은 객체를 생성하는 방식이나 구성 형태 표현 방식에 상관없이 생성되어 있는 객체를 사용하려 할 때 좋습니다. ProtoType을 위해서는 미리 객체를 다 생성해놓고 관리하고 있다가 사용하려 할 때 Prototype Manager에서 해당 객체를 Clone 하여 사용하면 됩니다. 그리고 동적으로 객체를 Manager에 등록, 삭제, 수정할 수 있기 때문에 일반적인 객체 생성 방식이 할 수 없는 객체 생성을 해줄 수 있습니다.

 

 

 

728x90