생성 패턴 - 프로토타입
The prototype pattern(이하 프로토타입 패턴)은 프로토타입(원형, 기본틀 등으로 해석할 수 있겠네요.) 객체에 의해 생성될 객체의 타입이 결정되는 생성 디자인 패턴이다. 이 패턴은 새로운 객체를 생성하기 위해 clone(복제)을 이용한다.
이 패턴은
- 클라이언트 어플리케이션에서 객체 생성자의 서브 클래싱을 피한다.(반대로 추상 팩토리 패턴에서는 객체 생성자를 서브 클래싱해야만 한다.)
- 주어진 애플리케이션에서 일반적인 방법(new 키워드를 이용한 방법)으로 객체를 생성할 때에 필요한 비용이 엄청난 경우에 이 비용을 없앨 수 있다.
이 패턴을 구현하기 위해, pure virtual(순수 추상 메서드라고 하면 되나요?... 전 원래 그냥 퓨어 버츄얼이라고 읽어서..) clone()메서드를 가지는 추상 클래스를 선언한다. polymorphic constructor(다형적인 생성자)가 필용한 어떤 클래스든지 추상 클래스를 상속받아서 clone()메서드를 구현해야 한다.
클라이언트는 new 연산자와 클래스 이름을 같이 쓰기보다는 프로토타입 객체의 clone()메서드를 호출하거나, 팩토리에 원하는 클래스에 해당하는 인자를 넣어서 호출하거나, 다른 디자인 패턴에 의해 제공되는 어떤 메커니즘을 통해 clone()메서드를 호출할 수도 있다.
구조 Structure
예 Example
프로토타입 패턴은 프로토타입 객체를 이용해서 어떤 종류의 객체를 생성할 지 정할 수 있다. 새로운 객체의 프로토타입은 종종 완전히 만들어지기 전에 생성이된다. 하지만 이 예에서는 프로토타입이 수동적이고(passive) 스스로는 복제하지는 않는다. 세포의 유사분열(두개의 독립적인 세포가 만들어진다.)은 프로토타입의 한 예이다. 유사분열은 스스로는 복제하는 일을 하기 때문에 프로토타입 패턴의 실례라 할 수 있다. 세포가 분열될 때, 두개의 독립적인 유전자 형이 결과물로 나온다. 다른 말로 세포가 스스로를 복제한다는 말이다.
Java
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
| /** * Prototype class */ abstract class Prototype implements Cloneable { @Override public Prototype clone() throws CloneNotSupportedException { return (Prototype) super .clone(); } public abstract void setX( int x); public abstract void printX(); public abstract int getX(); } /** * Implementation of prototype class */ class PrototypeImpl extends Prototype { int x; public PrototypeImpl( int x) { this .x = x; } public void setX( int x) { this .x = x; } public void printX() { System.out.println( "Value :" + x); } public int getX() { return x; } } /** * Client code */ public class PrototypeTest { public static void main(String args[]) throws CloneNotSupportedException { Prototype prototype = new PrototypeImpl( 1000 ); for ( int i = 1 ; i < 10 ; i++) { Prototype tempotype = prototype.clone(); // Usage of values in prototype to derive a new value. tempotype.setX( tempotype.getX() * i); tempotype.printX(); } } } /* **Code output** Value :1000 Value :2000 Value :3000 Value :4000 Value :5000 Value :6000 Value :7000 Value :8000 Value :9000 */ |
경험에 의한 규칙 Rules of Thumb
때때로 생성 패턴은 겹친다. - 프로토타입이나 추상팩토리 둘 중 하나가 적절한 경우가 있다.
또 다른 경우에 생성 패턴들은 상호 보완적이기도 하다. : 추상 팩토리는 제품 객체를 복제해서 리턴해주기 위해 프로토타입의 집합을 저장하고 있을 수 있다. 추상 팩토리, 빌더, 프로토타입은 각자의 구현에 싱글톤을 사용할 수도 있다.
추상 팩토리는 종종 팩토리 메서드로 구현되기도하지만(상속 inheritance을 통해 제품 객체를 생성하는 경우), 프로토타입을 이용해서 구현할 수도 있다.(위임 delegate를 이용해 생성하는 경우)
종종 설계자가 어디에서 더 유연한 설계가 필요한지를 발견하기 때문에, 설계는 팩토리 메서드를 사용해서 시작하고 추상 팩토리, 프로토타입, 똔느 빌더 등으로 발전할 수 있다.
프로토타입은 서브클래싱이 필요없지만, 초기화"initialize" 메서드를 필요로한다. 팩토리 메서드는 서브클래싱이 필요하지만, 초기화는 불필요하다.
콤포짓(Composite)과 데코레이터(Decorator)패턴을 많이 사용하는 설계에서는 종종 프로토타입을 사용하는 것이 좋을 수 있다.
경험에 의하면 런타임시에 당신이 복제하기 위하는 객체를 트루 카피(true copy)해서 생성하고 싶을 때 clone()메서드가 필요할 수 있다. True copy라하면 새로 생성되는 객체의 모든 속성(attributes)이 그를 복제해내는 객체와 같아야 한다는 것이다. 만약 당신이 new를 사용해서 그 클래스를 초기화할 수 있었다면, 초기값들을 속성으로 가진 객체를 얻어냈을 것이다. 예를 들어, 당신이 은행 거래를 수행하는 시스템을 설계했다고 하면, 당신은 자신의 계좌 정보를 가지고 있는 객체를 복사하고, 복제된 객체를 이용해 거래를 하고나서 수정된 객체로 기존 객체를 덮고 싶을 것이다. 이러한 경우에 당신은 new 대신 clone()을 사용하고 싶을 것이다.
이번 시간에는 prototype 패턴에 대해 알아보겠습니다.
객체 생성을 위한 디자인 패턴 중에서는 마지막 패턴이네요.
객체 생성을 위한 디자인 패턴 중에서는 마지막 패턴이네요.
1. 객체 생성을 위한 디자인 패턴
- Singleton(싱글톤) 패턴
- Abstract Factory 패턴
- Builder 패턴
- Factory Method 패턴
- Prototype 패턴
우리는 앞에서 객체를 생성해주는 여러가지 방식을 살펴보았습니다. 부분부분을 먼저 생성해서 최종으로 하나의 완성본을 만들기도 하고, 대행 함수를 통해 객체를 만들기도 했습니다.
이번에 배울 prototype 패턴은 “이미 생성된 객체를 복제해서 새로운 객체를 생성하는 방법“입니다.
이번에 배울 prototype 패턴은 “이미 생성된 객체를 복제해서 새로운 객체를 생성하는 방법“입니다.
이 패턴은 간단해서 이해하기는 쉽습니다. 이미 있는 객체를 복제한다는 것이 키 포인트입니다.
이러한 기존 객체 복제 방식은 아마도 게임에서 많이 사용할 것입니다. 게임에서는 다양한 캐릭터가 등장하고 이러한 캐릭터들이 게임 진행 상태에 따라 진화하여 또 다른 발전된 캐릭터로 발전하기도 합니다. 이러한 진화된 캐릭터는 또 다른 class 로 설계할 필요없이 모든 캐릭터가 진화 값에 따라 다른 동작을 하도록 하면 될 것입니다. 진화될 때마다 기존의 객체를 복제하고 진화 값을 설정해주면 다른 동작을 하도록 구현할 수 있습니다.
또한, 스타크래프트에서 저그같은 유닛을 계속 생산해내는 경우, 똑같은 캐릭터 객체를 계속 복제해서 생산해내면 아주 간단할 것입니다. 생산된 객체는 어차피 정확히 같으면 되니까요.
이러한 기존 객체 복제 방식은 아마도 게임에서 많이 사용할 것입니다. 게임에서는 다양한 캐릭터가 등장하고 이러한 캐릭터들이 게임 진행 상태에 따라 진화하여 또 다른 발전된 캐릭터로 발전하기도 합니다. 이러한 진화된 캐릭터는 또 다른 class 로 설계할 필요없이 모든 캐릭터가 진화 값에 따라 다른 동작을 하도록 하면 될 것입니다. 진화될 때마다 기존의 객체를 복제하고 진화 값을 설정해주면 다른 동작을 하도록 구현할 수 있습니다.
또한, 스타크래프트에서 저그같은 유닛을 계속 생산해내는 경우, 똑같은 캐릭터 객체를 계속 복제해서 생산해내면 아주 간단할 것입니다. 생산된 객체는 어차피 정확히 같으면 되니까요.
아래의 class 구조도와 간단한 코드를 보시죠.
// client operation
Prototype * base_object = new ConcretePrototype1; Prototype * clone_object = base_object->Clone(); |
Client 는 초기에 base_object 를 만들어두고 필요 시에 base_object 의 Clone 함수를 호출하여 똑같은 Prototype 객체를 만들어낼 수 있습니다.
prototype 패턴에 대한 sample 은 여러분도 쉽게 만들어낼 수 있을 것 같습니다. 여러분이 직접 만들어보시기 바랍니다.
힌트를 드리면, Prototype 은 virtual 로 추상 class 를 만들고, ConcreatePrototype 들은 Prototype class 를 상속하여 Clone 함수를 구현하면 됩니다. 즉, 자신을 정확히 복제하여 리턴하도록 구현하면 됩니다.
prototype 패턴에 대한 sample 은 여러분도 쉽게 만들어낼 수 있을 것 같습니다. 여러분이 직접 만들어보시기 바랍니다.
힌트를 드리면, Prototype 은 virtual 로 추상 class 를 만들고, ConcreatePrototype 들은 Prototype class 를 상속하여 Clone 함수를 구현하면 됩니다. 즉, 자신을 정확히 복제하여 리턴하도록 구현하면 됩니다.
다만 prototype 패턴을 구현 시에 가장 주의해야할 점이 있는데, 이는 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)입니다.
c++ 언어에서는 프로그래머가 별도로 복제 생성자(Copy Constructor)를 정의하지 않는 한 기본적으로 Shallow Copy 형태의 복제 생성자(clone())가 제공됩니다. 하지만, 이러한 기본적인 복제 생성자는 포인터 데이터 멤버를 사용할 경우 문제가 됩니다. 왜냐하면, 포인터 자체를 복제할 뿐 포인터 자체가 가리키는 내용물까지 모두 복제해주는 개념은 아니기 때문입니다.
이럴 경우 다양한 객체의 함수를 수행할 때 프로그래머가 이러한 사항을 모든 경우에서 인지하고 있지 못하면 자칫 프로그램이 Down 되거나 오동작을 일으킬 수 있습니다.
따라서, 보통은 데이터 멤버에 포인터 변수가 있는 경우 직접 깊은 복사 방식으로 구현해주는 것이 좋습니다.
c++ 언어에서는 프로그래머가 별도로 복제 생성자(Copy Constructor)를 정의하지 않는 한 기본적으로 Shallow Copy 형태의 복제 생성자(clone())가 제공됩니다. 하지만, 이러한 기본적인 복제 생성자는 포인터 데이터 멤버를 사용할 경우 문제가 됩니다. 왜냐하면, 포인터 자체를 복제할 뿐 포인터 자체가 가리키는 내용물까지 모두 복제해주는 개념은 아니기 때문입니다.
이럴 경우 다양한 객체의 함수를 수행할 때 프로그래머가 이러한 사항을 모든 경우에서 인지하고 있지 못하면 자칫 프로그램이 Down 되거나 오동작을 일으킬 수 있습니다.
따라서, 보통은 데이터 멤버에 포인터 변수가 있는 경우 직접 깊은 복사 방식으로 구현해주는 것이 좋습니다.
이러한 prototype 패턴의 경우 어떨 때 사용하면 편리할까요 ? 위에서 게임유닛에 대해서 잠깐 언급했지만 prototype 패턴을 사용하면 다음과 같은 점이 좋습니다.
- 실행할 객체가 run-time 에 결정될 경우 유용하다.
- 몇몇의 표본 객체를 만들어두면 이를 이용하여 무한정의 복제된 객체를 만들어낼 수 있다.
- Abstract Factory 나 Builder 패턴의 경우 생성될 객체의 자료형에 따라 객체를 생성해주기 위한 클래스들을 정의해야 하지만, prototype 패턴에서는 이것이 필요없다.
단점은 역시 각 ConcretePrototype 클래스들이 각자 모두 Clone 함수를 구현해야 한다는 것입니다.
참고로 프로토타입 매니저(Prototype Manager)라는 것이 있는데, 이는 복제할 원본 객체가 동적으로 추가, 삭제될 수 있는 환경에서 원본 객체들을 모아서 관리하는 역할을 담당하는 객체를 별도로 두는 것을 말합니다. 포토샾같은 그래픽 프로그램에서 파레트(Pallette)가 그러한 역할을 할 것입니다.
자, 여기까지 객체 생성을 위한 디자인 패턴을 모두 살펴보았습니다.
다음 시간부터는 “구조 개선을 위한 디자인 패턴”들을 공부해보도록 하겠습니다.
다음 시간부터는 “구조 개선을 위한 디자인 패턴”들을 공부해보도록 하겠습니다.
댓글 없음:
댓글 쓰기