Lee's Grow up

[디자인패턴/Design Patter] Prototype 패턴 프로토타입 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Patter] Prototype 패턴 프로토타입 패턴

효기로그 2019. 11. 27. 13:01
반응형

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문] 이라는 책의 공부 내용을 개인적으로 정리한 내용입니다.
처음 배우는 부분이기 때문에 틀린 부분이 있다면 지적해주시면 감사하겠습니다.

1. Prototype 패턴이란?


Prototype는 '원형' 이라는 의미로, 원형이 되는 인스턴스로 새로운 인스턴스를 만드는 방식, 즉 객체에 의해 생성될 객체의 타입이 결정되는 생성 디자인 패턴입니다.

2. Prototype 패턴의 등장인물


이번 포스팅에서 사용될 요소들의 역할입니다.

  • Prototype의 역할
    1. 인스턴스를 복사하여 새로운 인스턴스를 만들기 위한 메소드를 결정
  • ConcretePrototype
    1. 인스턴스를 복사해서 새로운 인스턴스를 만드는 메소드를 실제로 구현
  • Client
    1. 인스턴스 복사 메소드를 사용해서 새로운 인스턴스를 만듭니다.
Prototype 패턴의 클래스 다이어그램

3. 예제


이제 Prototype 패턴의 예제로 내용을 구체화 해보겠습니다.

예제에서 사용될 클래스 다이어그램

Shape 클래스

clone() 메소드를 사용하기 위해 Cloneable 인터페이스를 구현하며, 공통 메소드인 draw()는 추상 메소드로 정의하고,
하위 클래스에서 사용할 clone()는 공통으로 동작하는 메소드를 정의

public abstract class Shape implements Cloneable{
    protected Type type;

    abstract void draw();

    @Override
    public Object clone() throws CloneNotSupportedException {
        Object clone = null;

        try {
            clone = super.clone();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return clone;
    }
}

상황에 따라 clone()메소드를 공통으로 동작하지 않게 할 경우, 해당 클래스 자체를 interface로 선언 후,
clone()메소드 정의를 하위 클래스에 위임 하는 방식도 존재
여담으로 clone()try - catch 문에서는 어차피 Clonable 인터페이스를 구현했기 때문에 CloneNotSupportedException 이 발생하지 않는다. 그래서 추가로 RuntimeExceptioncatch문에 추가 해주는 방식이 좋다.

Circle, Triangle, Rectangle 클래스

각각 Shape 클래스를 상속 받으며 draw()메소드를 재정의한다. 해당 소스는 Circle 클래스 기준입니다.

public class Circle extends Shape {
    public Circle() {
        this.type = Type.CIRCLE;
    }

    @Override
    void draw() {
        System.out.println("[Circle]입니다.");    
    }
}

ShapeStroe 클래스

저장소를 담당하며, 최초 registerShap() 메소드 호출 시 복제에 사용할 객체를 인스턴스화 해서 shapeMap에 저장하는 동적을 하며, getShape()메소드를 통해 객체의 복사본을 반환해주는 역할을 합니다.

public class ShapeStore {
    private static Map<Type, Shape> shapeMap = new HashMap<Type, Shape>();

    public void registerShape() {
        Rectangle rec = new Rectangle();
        Circle cir = new Circle();
        Triangle tri = new Triangle();

        shapeMap.put(rec.type, rec);
        shapeMap.put(cir.type, cir);
        shapeMap.put(tri.type, tri);
    }

    public Shape getShape(Type type)  {
        return (Shape) shapeMap.get(type).clone();
    }
}

사람에 따라 해당 로직을 Client 해당 예제에서는 Main 클래스에서 정의하는 방식도 존재하지만,
저의 경우 Client와 역할을 분리해 해당 소스가 변경이 있어도 추가로 수정하는 부분이 없도록 역할을 분리해서 작성했습니다.

Main 클래스

마지막으로 Client에 해당하는 부분입니다. 복제된 객체를 사용합니다.

public class Main {
    public static void main(String[] args) {
        ShapeStore manager = new ShapeStore();
        manager.registerShape();

        Circle cir1 = (Circle)manager.getShape(Type.CIRCLE);
        cir1.draw();
        Circle cir2 = (Circle)manager.getShape(Type.CIRCLE);
        cir2.draw();

        Rectangle rec1 = (Rectangle)manager.getShape(Type.RECTANGLE);
        rec1.draw();

        Triangle tri1 = (Triangle)manager.getShape(Type.TRIANGLE);
        tri1.draw();

    }
}
실행 결과
[Circle]입니다.
[Circle]입니다.
[Rectangle]입니다.
[Triangle]입니다
그렇다면 왜 사용하는 것일까?
1. 객체의 생성이 값비싼 경우 ( DB를 참조하는 등 ) 객체 생성의 비용을 줄일 수 있습니다. 
고려할 사항

그러나, 예제에서 사용된 clone()메소드는 Object 클래스의 메소드로 써, 얕은 복사로 동작을 합니다.
그렇기 때문에 깊은 복제를 사용해야 할 경우 clone()메소드를 재정의 해야 합니다.
위와 같은 문제로 복제된 객체의

순환 참조나 깊은복사 얉은 복사에 대한 고민이 필요

4. 관련패턴


  1. Flyweight 패턴 : 프로토타입과 다르게 하나의 인스턴스를 복수의 장소에서 공유해서 이용
  2. Memento 패턴 : 스냅샷과 undo를 실행하기 위해 인스턴스의 상태를 저장합니다.
  3. CompositeDecorator 패턴 : 둘을 사용하면 복잡한 구조의 인스턴스가 동적으로 만들어지는 경우가 있는데, 이와 같은 경우 프로토 타입 패턴을 사용하면 편리합니다.
  4. Command 패턴 : 해당 패턴의 명령을 복제하고 싶은 경우 프로토타입 패턴을 사용
반응형
Comments