참고 : JAVA 언어로 배우는 디자인 패턴 입문
Prototype은 '원형'이나 '모범' 이라는 의미입니다. 원형이 되는 인스턴스, 모범이 되는 인스턴스를 기초로 새로운 인스턴스를 만드는 것입니다.
new 라는 키워드로 인스턴스를 만들 경우에는 클래스 이름을 반드시 지정해야 합니다. 그러나 클래스 이름을 지정하지 않고 인스턴스를 생성 할 때도 있습니다. 다음과 같은 경우에는 클래스로부터 인스턴스를 만드는 것이 아니라 인스턴스를 복사해서 새로운 인스턴스를 만듭니다.
1. 종류가 너무 많아 클래스로 정리되지 않는 경우
-> 첫 번째는 취급하는 오브젝트의 종류가 너무 많아서 각각을 별도의 클래스로 만들어 다수의 소스파일을 작성해야 하는 경우 입니다.
2. 클래스로부터 인스턴스 생성이 어려운 경우
-> 두 번째는 생성하고 싶은 인스턴스가 복잡한 작업을 거쳐 만들어지기 때문에 클래스로부터 만들기가 매우 어려운 경우입니다.
(예: 그래픽 에디터 마우스 조작)
3. Framework 와 생성할 인스턴스를 분리하고 싶은 경우
-> 인스턴스를 생성할 때의 Framework를 특정 클래스에 의존하지 않도록 만들고 싶은 경우입니다. 이 경우는 클래스 이름을 지정해서 인스턴스를 만드는 것이 아니라 이미 '모형' 이 되는 인스턴스를 등록해 두고, 그 등록된 인스턴스를 복사해서 인스턴스를 생성합니다.
인스턴스로부터 다른 인스턴스를 만드는 것이 복사기를 사용해서 서류를 복사하는 일과 비슷합니다. 원래의 서류를 어떻게 만들었는지 몰라도 복사기로 같은 종류의 서류를 몇 장이라도 만들 수 있습니다. 클래스로부터 인스턴스를 생성하는 것이 아니라 인스턴스로부터 별도의 인스턴스를 만드는 것을 Prototype 패턴 이라고 합니다.
package framework;
public interface Product extends Cloneable {
public abstract void use(String s);
public abstract Product createClone();
}
-> Product 인터페이스는 java.lang.Cloneable 인터페이스를 상속하고 있으며, 복제를 가능하게 합니다. Cloneable 인터페이스를 구현하고 있는 클래스의 인스턴스는 clone() 를 사용해서 자동적으로 복제를 할 수 있습니다.
// Product 인터페이스를 사용해서 인스턴스의 복제를 실행
package framework;
public class Manager {
private HashMap showcase = new HashMap();
public void register (String name, Product proto) {
showcase.put(name, proto);
}
public Product create(String protoname){
Product p = (Product) showcase.get(protoname);
return p.createClone(); // 복사된 값 리턴
}
}
-> register()에서 제품의 이름과 Product 인터페이스가 주어지면 그 한 쌍을 HashMap인 showcase에 등록합니다.
-> Product 인터페이스나 Manager 클래스의 소스에 MessageBox 클래스나 UnderlinePen 클래스의 클래스 이름이 전혀 나오지 않는 점에 주의! 클래스 이름이 나오지 않는다는 것은 Product와 Manager는 그들의 클래스와는 독립적으로 수정할 수 있다는 것을 의미합니다. 소스 안에 클래스 이름을 쓰면 그 클래스와 밀접한 관계가 생깁니다. 이 인터페이스만이 Manager 클래스와 다른 클래스의 다리 역할을 합니다.
public class MessageBox implements Product {
private char decochar;
public MessageBox(char decochar) {
this.decochar = decochar;
}
@Override
public void use(String s) {
int length = s.getBytes().length;
System.out.println("길이: "+ length);
for(int i = 0; i < length> System.out.print(decochar);
}
System.out.println(" ");
System.out.println(decochar + " " + s + " " + decochar);
for(int i = 0; i < length> System.out.print(decochar);
}
System.out.println(" ");
}
/*
* JAVA에서 clone()는 자신의 클래스 or 하위 클래스 에서만 호출할 수 있기 때문에
* 다른 클래스의 요청으로 복제를 하는 경우에는 createClone과 같은 다른 메소드를
* 이용해서 clone을 기술할 필요가 있습니다.
*/
@Override
public Product createClone() {
Product p = null;
try{
p = (Product) clone();
/*
* Cloneable 인터페이스를 구현한 클래스의 인스턴스는 clone 메소드를 호출하면 복사됩니다.
* 그리고 clone()의 반환값은 복사해서 만들어진 인스턴스가 됩니다.
* 내부에서 하는 일은 원래의 인스턴스와 같은 메모리를 확보해서, 그 인스턴스의 필드 내용을 복사하는 것입니다.
*/
} catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
} // end of MessageBox
public class Main {
public static void main(String[] args) {
// 준비
Manager manager = new Manager();
UnderlinePen upen = new UnderlinePen('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.register("strong message", upen);
manager.register("warning box", mbox);
manager.register("slash box", sbox);
// 생성 (clone() 된 값이 반환된다.)
Product p1 = manager.create("strong message");
p1.use("Hello, World");
Product p2 = manager.create("warning box");
p2.use("Hello, World");
Product p3 = manager.create("slash box");
p3.use("Hello, World")
}
}
Q. 왜 Prototype 패턴이 필요할까? (앞에서 언급했지만 다시 한번ㅎㅎ)
1. 종류가 너무 많아서 클래스로 정리할 수 없는 경우
~ 을 사용해서 문자열에 밑줄을 긋는 것
* 을 사용해서 문자열에 테두리를 긋는 것
/ 을 사용해서 문자열에 테두리를 긋는 것
… 등등
-> 이것을 모두 각각의 클래스로 만들면 클래스의 수가 너무 많아지기 때문에 소스 프로그램을 관리하기 힘들다.
2. 클래스로부터 인스턴스 생성이 어려운 경우
예: 마우스를 사용한 도형 에디터 같은 프로그램
3. Framework와 생성하는 인스턴스를 분리하고 싶은 경우
-> 예제에서 인스턴스의 복사(clone) 를 실행하는 부분을 framework 패키지 안에 설정합니다.
Manager 클래스의 create()는 클래스 이름 대신에 'strong message'나 'slash box'라는 문자열을 인스턴스 생성을 위한 이름으로 제공하고 있습니다. 이것은 JAVA라는 언어가 구비하고 있는 인스턴스 생성기구인 new Something() 이라는 형식을 보다 넓게 이용해서 클래스 이름의 속박으로부터 framework를 분리할 수 있습니다.
[참고]
Q. 소스 프로그램 내부에 클래스 이름이 쓰여 있으면 무엇이 문제일까요?
소스 내부에 이용할 클래스의 이름이 쓰여 있으면 그 클래스와 분리해서 재이용할 수 없게 됩니다.
JAVA 에서는 비록 클래스 파일(.class)만 가지고 있더라도 그 클래스를 재이용할 수 있는지가 중요합니다. 즉, 소스파일(.java)이 없어도 재사용할 수 있는지가 포인트입니다.
'Cloneable 인터페이스' 라고 하면 그 내부에 clone()가 선언되어 있는 것처럼 생각하기 쉽습니다. 그러나 Cloneable 인터페이스에는 메소드가 하나도 선언되어 있지 않습니다. 이 인터페이스는 단지 'clone에 의해 복사할 수 있다' 라는 표시로서 사용되고 있습니다. 이와 같은 표시를 하는 인터페이스를 maker interface라고 합니다.