반응형

의도

  • 원형이 되는(prototypical) 인스턴스를 사용하여 생성할 객체의 종류를 명시하고, 이렇게 만든 견본을 복사해서 새로운 객체를 생성한다.

 

활용성

  • 제품의 생성, 복합, 표현 방법에 독립적인 제품을 만들고자 할 때 사용한다.
  • 인스턴스화할 클래스를 런타임레 지정할 때(동적 로딩)
  • 제품 클래스 계통과 병렬적으로 만드는 팩토리 클래스를 피하고 싶을 때
  • 클래스의 인스턴스들이 서로 다른 상태 조합 중에 어느 하나일 때
  • 미리 원형으로 초기화해 두고, 나중에 이를 복제해서 사용하는 것이 매번 필요한 상태 조합의 값들을 수동적으로 초기화하는 것보다 더 편리할 수도 있다.

 

결과

  • 추상 팩토리 및 빌더와 비슷한 결과를 낳는다.
    • 사용자 쪽에는 어떤 구체적인 제품이 있는지 알리지 않아도 되기 때문에 사용자 쪽에서 상대해야 하는 클래스의 수가 적다.
    • 수정하지 않고도 응용프로그램에 따라 필요한 클래스들과 동작할 수 있게 된다.
  • 원형 패턴의 추가적 특성
    • 런타임에 새로운 제품을 추가하고 삭제할 수 있다.
      • 사용자에게 원형으로 생성되는 인스턴스를 등록하는 것만으로도 시스템에 새로운 제품 클래스를 추가할 수 있게 된다.
    • 값들을 다양화함으로써 새로운 객체를 명세한다.
      • 사용자와 동작할 원형에 해당하는 기존 클래스의 인스턴스를 만들어서 그 인스턴스를 등록하면, 사용자는 이 원형에 정의된 행동이 수행되어 마치 새로운 행동이 정의된 듯한 결과를 얻게 된다.
      • 시스템에 필요한 객체를 생성하기 위해 정의할 클래스의 수를 대폭 줄여주는 효과가 있다.
    • 구조를 다양화함으로써 새로운 객체를 명세할 수 있다.
      • 많은 응용프로그램은 구성요소와 부분 구성요소의 복합을 통해 객체를 구축한다.
      • 예) 회로설계편집기 - 세부 회로를 모아서 큰 회로를 만든다. 일반적으로 편의를 위해 복잡한 사용자 정의 구조(사용자가 만든 세부 회로 구조)를 사용자가 계속 이용할 수 있도록 해준다.
      • 이 경우, 이 사용자 정의 구조(사용자가 만든 세부 회로)를 원형으로 만들어, 이것을 현재 사용 가능한 회로 요소 관리 팔레트에 등록하면 이 복합 회로 객체가 Clone() 연산을 구현함으로써 다른 구조를 갖는 회로의 기본 골격을 만든다.
    • 서브클래스의 수를 줄인다.
      • 팩토리 메서드의 경우 Creator 클래스의 계통이 처리할 제품 관련 클래스의 계통과 병렬로 복합된다.
      • 원형 패턴에서는 팩토리 메서드에 새로운 객체를 만들어 달라고 요청하는 것이 아니라 원형을 복제하는 것이므로, Creator 클래스에 따른 새로운 상속 계층은 필요없다.
    • 동적으로 클래스에 따라 응용프로그램을 설정할 수 있다.
      • 몇몇 런타임 환경에서는 동적으로 클래스들을 응용프로그램으로 등록할 수 있도록 해 준다.
  • 원형 패턴을 쓸 때 신경 써야하는 가장 큰 걸림돌은 원형의 서브클래스가 Clone()연산을 구현해야 한다는 것이다.
    • Clone 연산 구현 시 얕은 복사, 깊은 복사에 대해 고려해야 한다.
    • 내부 상태를 초기화하기를 바랄 경우 원형 클래스의 연산을 이용하여 초기화해야 한다.
      • Clone 연산에 매개변수를 정의하게 되면 복제 인터페이스의 일관성이 없어진다.

 

협력 방법

  • 사용자는 원형 클래스에 스스로를 복제하도록 요청한다.

 

구조

 

실제 구현 구조

 

소스코드

//Client
public class ItemVendingMachine {
    private final List<Item> itemList;//원형 관리자(원형에 대한 정보를 검색하거나 저장 또는 삭제하는 기능을 담당)

    public ItemVendingMachine() {
        itemList = new ArrayList<>();
    }

    //아이템 등록(원형 등록)
    public void setItem(Item item) {
        itemList.add(item);
    }

    //아이템 삭제(원형 삭제)
    public void deleteItem(Item deleteItem) {
        for (int index = 0; index < itemList.size(); index++) {
            Item item = itemList.get(index);
            if(item.getClass().equals(deleteItem.getClass())) {
                Item removeItem = itemList.remove(index);
                System.out.println("remove Item : " + removeItem);
            }
        }
    }

    //아이템 뽑기(원형 복제), Operation()
    public Item getItem() {
        Random rn = new Random();
        Item item = itemList.get(rn.nextInt(itemList.size()));

        try {
            return item.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return item;
    }
}
//Prototype
public abstract class Item implements Cloneable {
    abstract void use();

    protected Item clone() throws CloneNotSupportedException {
        return (Item) super.clone();
    }
}
//ConcretePrototype
public class Apple extends Item {

    @Override
    protected Apple clone() throws CloneNotSupportedException {
        System.out.print(this + "-[사과 획득] ");
        return (Apple) super.clone();
    }

    @Override
    void use() {
        System.out.println(this + "-[사과 사용-포만감 회복]");
    }
}
//ConcretePrototype
public class HealthPotion extends Item {

    @Override
    protected HealthPotion clone() throws CloneNotSupportedException {
        System.out.print(this + "-[체력 물약 획득] ");
        return (HealthPotion) super.clone();
    }

    @Override
    void use() {
        System.out.println(this + "-[체력 물약 사용-체력 회복]");
    }
}
//ConcretePrototype
public class ManaPotion extends Item {

    @Override
    protected ManaPotion clone() throws CloneNotSupportedException {
        System.out.print(this + "-[마나 물약 획득] ");
        return (ManaPotion) super.clone();
    }

    @Override
    void use() {
        System.out.println(this + "-[마나 물약 사용-마나 회복]");
    }
}
//ConcretePrototype
public class Sword extends Item {

    @Override
    protected Sword clone() throws CloneNotSupportedException {
        System.out.print(this + "-[검 획득] ");
        return (Sword) super.clone();
    }

    @Override
    void use() {
        System.out.println(this + "-[검 사용-검 공격]");
    }
}
public class Main {

    public static void main(String[] args) {
        ItemVendingMachine itemVendingMachine = new ItemVendingMachine();
        itemVendingMachine.setItem(new Apple());
        itemVendingMachine.setItem(new Sword());
        itemVendingMachine.setItem(new ManaPotion());
        itemVendingMachine.setItem(new HealthPotion());

        //아이템을 뽑을 때 마다 원형에 자신의 복제를 요청하여 새로운 객체 생성
        for (int i = 0; i < 10; i++) {
            Item item = itemVendingMachine.getItem();
            item.use();
        }

        System.out.println("--------------------------");
        itemVendingMachine.deleteItem(new Apple()); //원형 관리자에서 Apple 원형 제거
        itemVendingMachine.deleteItem(new Sword()); //원형 관리자에서 Apple 원형 제거
        itemVendingMachine.deleteItem(new HealthPotion()); //원형 관리자에서 Apple 원형 제거
        System.out.println("--------------------------");

        for (int i = 0; i < 5; i++) {
            Item item = itemVendingMachine.getItem();
            item.use();
        }
    }
}
  • 아이템 자판기에서 등록된 원형을 이용해서 복제하고 실제 사용 시 복제된 객체를 사용한다.
  • 아이템 자판기에 등록된 원형을 일부 제거하여 뽑기 제품 범위를 변경할 수 있었다.

 

관련 패턴

  • 원형 패턴과 추상 팩토리 패턴은 경쟁적인 관계이지만 함께 사용될 수도 있다.
    • 추상 팩토리 패턴은 원형 집합을 저장하다가 필요할 때 복제하여 제품 객체를 반환하도록 사용할 수도 있다.
  • 복합체 패턴과 장식자 패턴을 많이 사용해야 하는 설계에서 원형 패턴을 쓰면 좋다.

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

http://www.cs.unc.edu/~stotts/GOF/hires/pat3dfso.htm

 

반응형

+ Recent posts