프로토타입 패턴

출처: Springframework Guru - Prototype pattern, Refactoring Guru - Prototype pattern
🚫 아래 내용은 주관적인 생각이므로 사실과 다를 수 있습니다.

Prototype Pattern
(프로토타입 패턴)

생성 패턴(Object Creational)

객체를 간단하게 복제하는 기능에 대한 패턴이다.
여기서의 복제는 그 목적에 따라 깊은 복사와 얕은 복사로,
요구되는 스펙에 맞게 선택해 구현한다.

구현에 요구되는 클래스는 아래와 같다.

  • Prototype: 복제를 허용하는 객체의 인터페이스 또는 추상 클래스
  • ConcretePrototype: 복제 기능을 제공하는 클래스
  • PrototypeManager: 프로토타입들을 생성하고 관리하는 클래스(생략 가능)
  • Client: PrototypeManager에게 Prototype 복제본을 요청하는 요청자

Class diagram

클래스_다이어그램

예시

// Prototype - 복제를 허용하는 객체의 인터페이스 또는 추상 클래스
public abstract class PrototypeCapableDoc implements Cloneable {
    private String data;

    public String getData() { return data; }

    public void setData(String data) { this.data = data; }

    // 복제 기능의 추상메소드
    public abstract PrototypeCapableDoc cloneDoc() throws CloneNotSupportedException;
}
// ConcretePrototype - 복제 기능을 제공하는 클래스(얕은 복사 예시)
public class ShallowCopyDoc extends PrototypeCapableDoc {
    @Override
    public PrototypeCapableDoc cloneDoc() {
        // 다른 참조 필드가 없기 때문에 얕은 복사로도 충분하다.
        try {
            return (ShallowCopyDoc) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
// 바로 밑의 DeepCopyDoc 클래스의 참조타입 필드가 되는 클래스.
// 깊은 복사의 과정을 보여주기 위한 예시
public class InnerObj implements Cloneable {
    private String name;
    
    public String getName() { return name; }
    
    public void setName(String name) { this.name = name; }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
// ConcretePrototype - 복제 기능을 제공하는 클래스(깊은 복사)
public class DeepCopyDoc extends PrototypeCapableDoc {
    private InnerObj innerObj; // 참조타입 필드
    
    public InnerObj getInnerObj() { return innerObj; }

    public void setInnerObj(InnerObj innerObj) { this.innerObj = innerObj; }

    @Override
    public PrototypeCapableDoc cloneDoc() throws CloneNotSupportedException {
        // 깊은 복사 추상메소드 구현
        DeepCopyDoc dcDoc = (DeepCopyDoc) super.clone();
        // 참조타입 필드의 데이터도 확실하게 복제해야한다.
        InnerObj clonedInnerObj = (InnerObj) dcDoc.getInnerObj().clone();
        dcDoc.setInnerObj(clonedInnerObj);
        return dcDoc;
    }

}
// PrototypeManager - 프로토타입들을 생성하고 관리하는 클래스(생략 가능)
// 관리자를 따로 분리함으로써 프로토타입의 오염을 방지한다
public class DocPrototypeMgr {
    private static Map<String, PrototypeCapableDoc> prototypes = new HashMap<String, PrototypeCapableDoc>();
    static {
        // 원본이 되는 프로토타입들을 생성, 키워드로 저장
        ShallowCopyDoc scDoc = new ShallowCopyDoc();
        scDoc.setData("ShallowCopyDoc data...");
        prototypes.put("SCDoc", scDoc);

        InnerObj innerObj = new InnerObj();
        innerObj.setName("InnerObj name...");

        DeepCopyDoc dcDoc = new DeepCopyDoc();
        dcDoc.setData("DeepCopyDoc data...");
        dcDoc.setInnerObj(innerObj);
        prototypes.put("DCDoc", dcDoc);
    }

    // 요청시, 프로토타입들의 복제본을 반환
    public static PrototypeCapableDoc getClonedDoc(final String type) {
        try {
            PrototypeCapableDoc doc = prototypes.get(type);
            return doc.cloneDoc();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

테스트

PrototypeCapableDoc clonedSCDoc1 = DocPrototypeMgr.getClonedDoc("SCDoc");
PrototypeCapableDoc clonedSCDoc2 = DocPrototypeMgr.getClonedDoc("SCDoc");
// clonedSCDoc1.getData(): "ShallowCopyDoc data..."
// clonedSCDoc2.getData(): "ShallowCopyDoc data..."
clonedSCDoc2.setData("Some data 2");
// clonedSCDoc1.getData(): "ShallowCopyDoc data..."
// clonedSCDoc2.getData(): "Some data 2"

PrototypeCapableDoc clonedDCDoc1 = DocPrototypeMgr.getClonedDoc("DCDoc");
PrototypeCapableDoc clonedDCDoc2 = DocPrototypeMgr.getClonedDoc("DCDoc");
// clonedDCDoc1.getInnerObj().getName(): "InnerObj name..."
// clonedDCDoc2.getInnerObj().getName(): "InnerObj name..."
dcDoc2 = (DeepCopyDoc) clonedDCDoc2;
dcDoc2.getInnerObj().setNata("Some name 2");
// clonedDCDoc1.getInnerObj().getName(): "InnerObj name..."
// clonedDCDoc2.getInnerObj().getName(): "Some name 2"

장점

  • 구현 클래스와의 연결점이 없어도 복제가 가능
    • Object clonedObj = cloneableObj.clone();
  • 이미 완성된 프로토타입과 중복되는 초기화 코드 삭제 가능
  • 복잡한 객체를 간편하게 생성 가능
  • 상속 관계 발생을 줄여준다
    • 복제 후 변경해서 사용하는 것이 서브 클래싱의 대안이 될 수 있다
  • 런타임에서 객체의 추가 또는 삭제가 가능하다

단점

  • 순환참조되는 복잡한 객체의 경우 굉장히 번거로워질 수 있다
  • 원시 타입을 제외한 내부 필드의 클래스들도 모두 복제 추상메소드를 구현해야 한다
  • 기존의 클래스에 복제 기능을 추가하는것은 번거로워질 수 있다

댓글남기기