5.    The Prototype Pattern

Prototype패턴은 특정 클래스들이 최종 클래스를 구성하는 컴포넌트나 상세 내역을 결정한다는 점에서Builder패턴과 유사하다.그러나 목표가 되는 클래스들이 한 개 이상의Prototype클래스를 복제하여 내용을 원하는대로 변경해서 생성된다는 점이 다르다.

 

참고:자바에서 복제하기

Clone메소드를 사용하면 모든 종류의 자바 객체를 복제할수 있다.

 

Jobj j1 = (Jobj)j0.clone();

 

Clone메소드는Object의 반환형을 갖기 위해 항상 선언된 객체를 반환 한다.따라서 개발자가 복제하는 객체의 실제형에 복사된 객체의 형을 변환해야 할것이다.다음은 이 메소드에 적용되는 세 가지의 중요한 주의 사항이다.

l       Protected로 설정된 메소드여야 하며,동일한 클래스나 서브 클래스 안에서만 호출될 수 있다.

l       Coneable인터페이스를 구현하기 위해 선언된 객체들만 클론 작업을 수행할 수 있다.모든 배열은Coneable인터페이스를 구현하는 것으로 간주된다.

l       Object클래스에 속한 모든 객체는Coneable인터페이스를 구현하지 않으면, CloneNotSupported라는 예외가 발생한다.

 

clone메소드가protected로 설정되기 때문에 위의 내용은 해당 클래스 내부에clone메소드를public으로 포함하도록 권장한다.이것은 클래스가protectedclone메소드를 사용할 수 있는 상황에 적용된다.

 

Public abstract class SwimData implements Cloneable {

            Public object cloneMe() throws CloneNotSupportException {

                        Return super.clone();

            }

}

 

개발자는 복제된 클래스 안의 데이터나 처리 메소드의 내용을 변경하는 한정적인 복제 작업 내용을clone메소드에 전달하는 아규먼트의 내용에 따라 설정할 수도 있다.

 

참고:직렬화(Serialization)를 이용한 복제

직렬화 가능한 인터페이스를 사용하여 복제를 수행하는 좋은 방법이 있다.하나의 클래스를 직렬화가 가능하다고 정의하려면,해당 클래스를 바이트 형식으로 작성한 후 해당 클래스를 재작성하기 위해서 해당 바이트 정보를 읽어들이는 방식으로 만들어야 한다.이것은java RMI및 자바 빈이 구현되는 방식이기도 하다.

 

다음은SwimmerSwimData클래스 모드를 직렬화 가능하게 선언할 경우이다.

public class SwimData implements Cloneable, Serializable

class Swimmer implements Serializable

 

해당 바이트 정보를 결과값으로 작성하여 클래스의 해당 인스턴스에 대한 완벽한 데이터 복사를 수행하기 위해 해당 바이트 정보를 읽어 들일 수 있다.

 

public Object deepClone(){

            try{

                        ByteArrayOutputStream b = new ByteArrayOutputStream();

                        ObjectOutputStream out = new ObjectOutputStream(b);

                        out.writeObject(this);

                        ByteArrayInputStream bIn = new

                        ByteArrayInputStream(b.toByteArray());

                        ObjectInputStream oi = new ObjectInputStream(bIn);

                        return (oi.readObject());

            }catch (Exception e){

                        System.out.println("exception:"+e.getMessage());

                        return null;

            }

}

 

deepClone메소드는 어떤 종류의 복잡한 클래스라도 해당 인스턴스를 복사할수 있게 한다.그리고 클래스가 포함하는 모든 종류의 클래스들이 직렬화가 가능하도록 선언될수 있는 한 해당 데이터가 두개의 원본과 사본 사이에서 독립적으로 운용될 수 있도록 만들어 준다.

 

구조

사용자 삽입 이미지

 

역할

v     Prototype의 역할

인스턴스를 복사해서 새로운 인스턴스를 만들기 위한 메소드를 결정합니다.

 

v     ConcretePrototype의 역할

인스턴스를 복사해서 새로운 인스턴스를 만든는 메소드를 실제로 구현합니다.

 

의도

프로토타입 패턴은 매우 시간을 많이 소비하거나 좀 복잡한 클래스의 인스턴스를 생성할 때 사용된다.그래서,더 많은 인스턴스를 생성하는 것 보다는 인스턴스 원본의 사본을 만들어 적당하게 수정하는 것이 낫다.프로토타입들을 그것들이 제공하는 처리과정의 타입에서만 다른 클래스들을 필요로 할 때마다 사용되어질 수 있다

 

적용시기

다음과 같은 경우에 클래스로부터 인스턴스를 만드는 것이 아니라 인스턴스를 복사해서 새로운 인스턴스를 만든다.

v     종류가 많아 한 개의 클래스로 할 수 없는경우:취급하는 객체의 종류가 너무 많아서 하나씩 다른 클래스로 만들면 다수의 소스파일을 작성해야 하는 경우

v     클래스로부터 인스턴스를 생성하기 어려운경우:생성하려는 인스턴스가 복잡한 과정을 거쳐 만들어지기 때문에 클래스로부터 만드는 것이 매우 어려운 경우,예를 들어 그래픽에디터에서 사용자의 마우스조작에 의해 만들어진 인스턴스를 프로그래밍으로 작성하는 것은 어렵다.사용자의 조작으로 만들어진 인스턴스를 다시 만들고 싶을때에는 지금 만든 인스턴스를 일단 보존해 두었다가 필요한 경우 그것을 복사합니다.

v     프레임워크와 생성할 인스턴스를 분리하고 싶을 때:인스턴스를 생성할때의 프레임워크를 특정 클래스에 의존하지 않도록 만들고 싶은 경우.이와 같은 경우에는 클래스의 이름을 지정해서 인스턴스를 만드는 것이 아니라 미리모형이 되는 인스턴스를 등록해놓고,등록된 인스턴스를 복사해서 인스턴스를 생성한다.인스턴스로부터 다른 인스턴스를 만들어 내는 것은 진열장 속의 케이크가 어떻게 만들어지고 이름이 무엇인지도 모르면서 케익을 주문하는것과 같다.

 

효과

Prototype패턴을 사용하면 다음과 같은 효과를 얻을수 있다.

l       필요한 경우 클래스를 복제해서 실행중에 클래스를 추가하거나 삭제할 수 있다.

l       프로그램의 상태에 따라 실행중에도 클래스의 내부 데이터 표현 방식을 갱신할 수있다.

l       클래스의 추가 및 상속 구조의 설정없이 실행중에도 새로운 객체를 만들어낼수 있다.

주의점

Prototype패턴을 구현하는 작업에서 한 가지 어려운 점은 해당 클래스가 이미 존재할 경우clone메소드나deepClone메소드가 필요해도 해당 메소드들을 추가하기 위해 클래스의 내용을 변경하지 못할 수도 있다는 것이다.특히 클래스에 포함된 모든 클래스 객체들을 직력화가 가능한도록 선언할 수 없을 때deepClone메소드를 사용하는 것은 어렵다.또한 다른 클래스에 대하여 순환참조값을 갖는 클래스의 경우에는 복제될 수 없다.

 

 

예제소스

사용자 삽입 이미지

 

다음은 예제소스이다.

package framework;

 

public interface Product extends Cloneable {

   public abstract void use(String s);

   public abstract Product createClone();

}

 

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;

   }

}

 

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;

   }

}

 

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

       return p.createClone();

   }

}

 

import framework.*;

 

public class mainClass{

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

   }

}

 

관련패턴

사용자 삽입 이미지
      Factory Method :제공하는 데이터의 내용을 기준으로 다수의 유사한 클래스에서 하나의 클래스에 대한 인스턴스를 선택하고 반환하기 위해 사용된다.

사용자 삽입 이미지
      Abstract Factory :클래스들에 대한 여러가지 그룹중 한가지를 반환한다.일부에서는 실제로 해당 클래스들의 그룹에 대한Factory패턴을 반환하기도 한다.

사용자 삽입 이미지
      Builder :제시된 클래스와 함께 제공되는 데이터의 내용에 따라 새로운 객체를 만들기 위해 다수의 객체들을 조합한다. Factory패턴을 이용하여 어떤 방식으로 객체를 조합할 것인지 선택하기도 한다.

사용자 삽입 이미지
      Singleton :하나의 객체에 대하여 단 하나의 인스턴스만 존재하도록 하며,그것에 따라 해당 인스턴스에 대해 전역적으로 접근한다.

'Programming > Design Pattern' 카테고리의 다른 글

[펌] The Decorator Pattern  (0) 2006.01.21
[펌] The Builder Pattern  (0) 2006.01.21
[펌] Chapter 4 Structual Patterns(구조 패턴)  (0) 2006.01.21
[펌] The Adapter Pattern  (0) 2006.01.21
[펌] The Builder Pattern  (0) 2006.01.21

Posted by 영웅기삼
,