본문 바로가기

Design Pattern

[Java-Pattern] Prototype

 

인스턴스를 생설할때 new를 사용하는 것이 아니라 이미 만들어진 인스턴스를 복사해서


새로운 인스턴스를 만들어 내는 패턴이다. 이 인스턴스 생성 방식을 사용 하는 이유는


1. 종류가 너무 많아 한개의 클래스로 할 수 없는 경우

2. 클래스로부터 인스턴스를 생성하기 어려운 경우

3. 프레임워크와 생성할 인스턴스를 분리하고 싶은 경우


정도로 나눌 수 있다.


예를 보면..

Product 인터페이스와 Manager 클래스는 framework패키지에 속해있고, 인스턴스를 복제하는 일을 한다.


Manager클래스는 createClone을 호출하지만 구체적의 어느 클래스의 인스턴스인지 까지는 알지 못 하며 단지 Product인터페이스를 구현한 클래스의 인스턴스라면 복제가 가능하다.


MessageBox클래스와 UnderlinePen클래스는 둘 다 Product 인터페이스를 구현하고 있으며, 이들의 인스턴스를 만들어 Manager클래스에 등록해두면 언제든지 복제 할 수 있다.


여기서 사용된 복제는 clone()메소드를 사용하였으며, java.lang.Cloneable인터페이스를 구현하고 있는 클래스의 인스턴스는 clone메소드를 사용해서 자동적으로 자기자신을 복제 할 수 있다.



코드를 보면

1. Product인터페이스

package framework;

public interface Product extends Cloneable {
    public abstract void use(String s);
    public abstract Product createClone();
}

2.Manager클래스

package framework;
import java.util.*;

public class Manager {
    private Hashtable showcase = new Hashtable();
    public void register(String name, Product proto) {
        showcase.put(name, proto);
    }
    public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname); //Upcasting
        return p.createClone();
    }
}

이름과 인스턴스의 대응관계를 해쉬 테이블을 사용해 저장하며, create에서는 Product 인터페이스를 구현한 클래스의 인스턴스를 복사하여 return해주고 있다.


3.MessageBox

String을 주어진 char형으로 감싸 출력하는 클래스이다.

import framework.*;

public class MessageBox implements Product {
    private char decochar;
    public MessageBox(char decochar) {
        this.decochar = decochar;
    }
    public void use(String s) {
        int length = s.getBytes().length;
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
        System.out.println(decochar + " "  + s + " " + decochar);
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
    }
    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}

Product 인터페이스만 구현되어 있지만, Product인터페이스는 java.lang.Cloneable인터페이스를 확장한 것이기 때문에 clone()사용이 가능하다. 또한 clone()메소드는 자신의 클래스(및 하위 클래스)에서만 호출할 수 있기 때문이 (Protected형) 다른 클래스의 요청으로 복제하는 경우에는

다른 메소드로 clone을 감싸줄 필요가 있다.


4.UnderlinePen클래스

주어진 캐릭터로 String에 밑줄을 그어 출력하는 클래스이다.

import framework.*;

public class UnderlinePen implements Product {
    private char ulchar;
    public UnderlinePen(char ulchar) {
        this.ulchar = ulchar;
    }
    public void use(String s) {
        int length = s.getBytes().length;
        System.out.println("\""  + s + "\"");
        System.out.print(" ");
        for (int i = 0; i < length; i++) {
            System.out.print(ulchar);
        }
        System.out.println("");
    }
    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

}


Main클래스(테스트를 위함)

import framework.*;

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);

        // 생성
        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.");
    }
}

이제 하나를 잡아서 따라가보자..

Main에서 최초로 Manager manager = new Manager();
 클래스를 생성했고,
UnderlinePen upen = new UnderlinePen('~');

upen이란 이름으로 UnderlinePen클래스의 인스턴스를 생성해주고 있다.


그리고,

manager.register("strong message", upen);
Manager클래스의 register 메소드를 사용하여 해쉬 테이블에 strong message라는 이름으로

upen인스턴스를 연결시켜 주고있다.


Product p1 = manager.create("strong message");
Product형의 p1은 manager인스턴스의 create메소드를 사용하여 그 값을 받는데

create("strong message")를 살펴보면


public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname); //Upcasting
        return p.createClone();
    }

아까 register를 사용하여 해쉬테이블에 등록한 인스턴스들 중 protoname즉 strong message라는 이름을 가진 것을 꺼내어 Product p에 대입시키고 있으며(Product형으로 Upcasting)

, 이 p의 createClone()을 실행

시킨 후 그 리턴값을 리턴해주고 있다.


crateClone()을 보면

    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

(Product)clone(); 자기 자신을 복사하여 (Product형으로 Upcastiong된 UnderlinePen클래스의 인스턴스) 그 리턴값을 Prodcut형으로 DownCasting 해주고(clone의 리턴형이 object형이므로)

그 값을 리턴해준다. 즉 Product형으로 형변환된 복제된 UnderlinePen클래스의 인스턴스가 리턴되는 것이다.

 

그래서 Main에서 Product p1 = manager.create("strong message");

 

이 문장에서 p1에는 Product형으로 형변환된 복제된 UnderlinePen클래스의 인스턴스가 대입되게 되고 p1.use를 실행 하면


 p1.use("Hello, world.");

Hello,world밑에 밑줄이 그어져서 출력되게 된다.