4 분 소요

Prototype은 미리 정의된 개체로부터 복제하여 개체를 생성합니다. 속성까지 동일한 개체를 여러개 만들때 사용하면 좋습니다.

설명

다음과 같이 편집 프로그램에서 사용하는 도형 팔레트 메뉴를 생각해 봅시다.

Image

각 도형은 지오메트리 정보와 배경 속성, 테두리 속성을 가지고 있는데요, 메뉴를 클릭할 때마다 해당 속성을 설정해서 개체를 생성해 주어야 합니다. 이 값을 코드에서 일일이 설정하는 건 번거로운 일이죠. 괜히 코드량만 많아져 눈을 어지럽힐 수 있습니다. 코드 냄새도 나고요.

이러한 경우 Prototype을 이용하여 미리 정의된 개체를 메뉴에 연결해 두고, 메뉴 클릭시 개체를 복제해서 사용하면 좀더 깔끔하게 구현할 수 있습니다.

다음 그림은 Prototype의 일반적인 구조입니다.

Clientm_PrototypeClone()을 호출하여 개체를 복제 생성합니다.

Prototype

항목 내용
Prototype Clone() 을 제공하는 추상 클래스입니다. 자식 개체에서 Clone()을 구현해야 합니다.
ConcretePrototype1, ConcretePrototype2 Clone()을 구체화한 개체입니다.
Client PrototypeClone()을 이용하여 새로운 개체를 만듭니다.

특징

도형 팔레트와 같이 UI를 통해 미리 속성(개체 크기, 색상 등)을 설정한 개체를 생성할때 좋습니다. 또한, 사용자 정의한 개체를 UI에 추가하여 상호작용할 수도 있습니다.

예제

다음은 ShapePalette에 다양한 ShapeButton을 두고, ShapeButtonClick()하면 미리 정의된 Shape을 복제하여 사용하는 예입니다. 또한 #4와 같이 사용자 정의한 개체를 UI에 등록하여 사용할 수 있습니다.

  1. #1 : Shape, Rectangle, CircleClone()을 제공하며, 자기 자신을 복제합니다.
  2. #2 : ShapeButton 생성시 사용할 Shape개체를 연결합니다.
  3. #3 : ShapeButtonClick()하면 복제본을 생성합니다.
  4. #4 : 사용자가 정의한 ShapeShapeButton에 등록하여 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// ----
// #1. Shape, Rectangle, Circle은 Clone()을 제공하며, 자기 자신을 복제합니다.
// ----
class Shape {
protected:
    Shape() = default; // 다형 소멸을 제공하는 추상 클래스. 상속해서만 사용하도록 protected
    Shape(const Shape&) = default; 
public:
    virtual ~Shape() = default; // 다형 소멸 하도록 public virtual 
private:
    Shape(Shape&&) = delete; 
    Shape& operator =(const Shape&) = delete; 
    Shape& operator =(Shape&&) = delete;   
public:
    // #1. 복제본을 생성합니다.
    virtual std::unique_ptr<Shape> Clone() const = 0;
};

class Rectangle : public Shape {
    int m_Left;
    int m_Top;
    int m_Width;
    int m_Height;
public:
    Rectangle(int l, int t, int w, int h) : m_Left{l}, m_Top{t}, m_Width{w}, m_Height{h} {}
private:
    Rectangle(const Rectangle&) = default; // Clone 에서만 사용하기 때문에 private입니다.
public:
    int GetLeft() const {return m_Left;}
    int GetTop() const {return m_Top;}
    int GetWidth() const {return m_Width;}
    int GetHeight() const {return m_Height;}

    void SetLeft(int val) {m_Left = val;}
    void SetTop(int val) {m_Top = val;}
    void SetWidth(int val) {m_Width = val;}
    void SetHeight(int val) {m_Height = val;}   

    // #1. 복제본을 생성합니다.
    virtual std::unique_ptr<Shape> Clone() const override {
        return std::unique_ptr<Shape>{new Rectangle(*this)};
    }
};
class Circle : public Shape {
    int m_CenterX;
    int m_CenterY;
    int m_Diameter;
public:
    Circle(int centerX, int centerY, int diameter) : m_CenterX(centerX), m_CenterY(centerY), m_Diameter(diameter) {}
private:
    Circle(const Circle&) = default; // Clone 에서만 사용하기 때문에 private입니다.
public:
    int GetCenterX() const {return m_CenterX;}
    int GetCenterY() const {return m_CenterY;}
    int GetDiameter() const {return m_Diameter;}

    void SetCenterX(int val) {m_CenterX = val;}
    void SetCenterY(int val) {m_CenterY = val;}
    void SetDiameter(int val) {m_Diameter = val;}

    // #1. 복제본을 생성합니다.
    virtual std::unique_ptr<Shape> Clone() const override {
        return std::unique_ptr<Shape>{new Circle(*this)};
    }
};

// ----
// #2. Client 입니다. 클릭하면 해당 Shape개체를 생성하기 위해 Shape 개체를 연결합니다.
// ----
class ShapeButton {
    std::unique_ptr<Shape> m_Shape; // #2. Shape 개체를 연결합니다.
public:
    explicit ShapeButton(std::unique_ptr<Shape> shape) : m_Shape{std::move(shape)} {} // #2
private:
    ShapeButton(const ShapeButton&) = default; 
    ShapeButton(ShapeButton&&) = delete; 
    ShapeButton& operator =(const ShapeButton&) = delete; 
    ShapeButton& operator =(ShapeButton&&) = delete;   
public:

    // ----
    // #3. 관라하는 m_Shape의 복제본을 리턴합니다.
    // ----
    std::unique_ptr<Shape> Click() const {
        if(!m_Shape) {
            return std::unique_ptr<Shape>{};
        }

        return m_Shape->Clone();
    }
    // #4. 사용자의 개체를 등록합니다.
    void SetShape(std::unique_ptr<Shape> shape) {
        m_Shape = std::move(shape);
    }
};

// ShapeButton에 미리 정의된 도형들을 연결합니다.
class ShapePalette {
    ShapeButton m_RectangleButton;
    ShapeButton m_SquareButton;
    ShapeButton m_CircleButton;
    ShapeButton m_UserButton;

public:
    ShapePalette() :
        m_RectangleButton{std::unique_ptr<Shape>{new Rectangle{1, 2, 5, 10}}},
        m_SquareButton{std::unique_ptr<Shape>{new Rectangle{1, 2, 20, 20}}}, // 너비와 높이가 동일한 Rectangle입니다.
        m_CircleButton{std::unique_ptr<Shape>{new Circle{1, 2, 30}}},
        m_UserButton{std::unique_ptr<Shape>{}} {}
private:
    ShapePalette(const ShapePalette&) = delete; 
    ShapePalette(ShapePalette&&) = delete; 
    ShapePalette& operator =(const ShapePalette&) = delete; 
    ShapePalette& operator =(ShapePalette&&) = delete;       
public:
    const ShapeButton& GetRectangleButton() const {return m_RectangleButton;}
    const ShapeButton& GetSquareButton() const {return m_SquareButton;}
    const ShapeButton& GetCircleButton() const {return m_CircleButton;}
    const ShapeButton& GetUserButton() const {return m_UserButton;}

    void SetUserButton(std::unique_ptr<Shape> shape) { // #4
        m_UserButton.SetShape(std::move(shape));
    }
};

// ----
// 테스트 코드
// ----
ShapePalette shapePalette;

// #3. 각 버튼에 연결된 shape을 복제해서 사용합니다.
std::unique_ptr<Shape> shape1{shapePalette.GetRectangleButton().Click()};
const Rectangle& rectangle{dynamic_cast<const Rectangle&>(*shape1)};
EXPECT_TRUE(rectangle.GetLeft() == 1 && rectangle.GetTop() == 2 && rectangle.GetWidth() == 5 && rectangle.GetHeight() == 10);

std::unique_ptr<Shape> shape2{shapePalette.GetSquareButton().Click()};
const Rectangle& square{dynamic_cast<const Rectangle&>(*shape2)};
EXPECT_TRUE(square.GetLeft() == 1 && square.GetTop() == 2 && square.GetWidth() == 20 && square.GetHeight() == 20);

std::unique_ptr<Shape> shape3{shapePalette.GetCircleButton().Click()};
Circle& circle{dynamic_cast<Circle&>(*shape3)};
EXPECT_TRUE(circle.GetCenterX() == 1 && circle.GetCenterY() == 2 && circle.GetDiameter() == 30);

// #4. circle의 지름을 수정하고 해당 shape을 사용자 버튼에 추가합니다.
circle.SetDiameter(100);
shapePalette.SetUserButton(std::move(shape3));

// 사용자가 추가한 개체를 복제해서 사용합니다.
std::unique_ptr<Shape> shape4{shapePalette.GetUserButton().Click()};
Circle& user{dynamic_cast<Circle&>(*shape4)};
EXPECT_TRUE(user.GetCenterX() == 1 && user.GetCenterY() == 2 && user.GetDiameter() == 100);

댓글남기기