공부/디자인 패턴

[디자인패턴/Java] 원형 패턴( Prototype Pattern)

감자 바보 2022. 4. 11. 15:06
반응형

[디자인패턴] 원형 패턴( Prototype Pattern)

의도 : 원형이 되는(Prototype) 인스턴스를 사용하여 생성할 객체 종류를 명시하고 견본을 복사해 새 객체를 생성한다.

 

 

본문

 

 원형 패턴은 제품의 생성, 복합 표현 방법에 독립적인 제품을 만들고자 할 때 사용한다.

 

활용성

제품의 생성, 복합, 포현 방법에 독립적인 제품을 만들고자 할 때 사용한다.

  • 인스턴스화할 클래스를 런타임에 지정하는 경우
  • 제품 클래스 계통과 병렬적으로 만드는 팩토리 클래스를 피하고 싶은 경우
  • 클래스의 인스턴스들이 서로 다른 상태 조합 중 어느 하나인 경우

 

UML

Prototype : 자신을 복제하는 데 필요한 인터페이스를 정의한다.

ConcretePrototype : 자신을 복제하는 연산을 구현한다.

Client : 원형에 자기 자신의 복제를 요청하여 새로운 객체를 생성한다. 

 

결과

  1. 런타임에 새로운 제품을 추가 및 삭제할 수 있다.
  2. 값들을 다양화하여 새로운 객체를 명세한다.
    • 새 클래스 생성 대신에 객체의 변수 값을 지정하는 식으로 새로운 행동을 정의할 수 있다. 
  3. 구조를 다양화하여 새로운 객체를 명세한다. 
    • ex) 사용자 정의 구조를 등록하고 이를 clone 하는 방식
  4. 서브클래스의 수를 줄일 수 있다. 
    • 객체를 생성하는 것이 아닌 원형을 복제하는 식으로 새로운 상속 계층을 줄일 수 있다. 
  5. 동적으로 클래스에 따라 응용프로그램을 설정할 수 있다.

 

구현

 c++ 같은 정적 언어에서 유용하다.

  1. 원형 관리자(Prototype Manager) 사용
    1. 원형관리자는 특정 키에 부합되는 원형을 저장하고, 찾아서 반환하거나 삭제하는 기능을 담당하는 저장소이다.
    2. 런타임에 레지스트리를 변경하거나 검색할 수 있으며 이를 통해 코드 외에도 이러한 관리 기능을 이용하거나 확장할 수 있다.
    3. 각 프로토타입을 특정 키 값에 매칭시키는 방식을 이용
  2. Clone() 연산 구현
    1. 원형 패턴에서 Clone을 얕은 복사와 깊은 복사 중 어떤 방식을 사용할 지 생각할 필요가 있음.
      1. 얕은 복사 : 특정 객체가 저장된 주소를 복사하는 것. B가 A를 얕은 복사한 경우 B를 수정한다면 A도 수정되게 됨.
      2. 깊은 복사 : 특정 객체의 필드 값들을 복사하는 것. 얕은 복사와 다르게 B가 A를 수정하더라도 A는 수정되지 않음.
      3. Save, Load 연산이 정이되어 있으면 Save 연산으로 메모리 버퍼에 저장하고 Load로 메모리 버퍼에 저장된 객체를 복사하는 식으로 구현이 가능하다.
  3. Clone 초기화
    1. 사용자가 내부 필드를 자신이 선택한 값으로 초기화 하길 원할 수 있음. 이런 경우 객체 복제 이후 이를 수행하는 Initialize() 등의 이름을 가진 메서드를 사용자에게 제공해야함.

 

예시

 Clone을 통해 제품을 복제하여 제품을 생성한다.

 

UML

 

Display.java

더보기
public class Display {
    public Display() {
        _producer = "기본";
    }
    public Display(Display other) {
        _producer = other._producer;
    }

    public void testDisplay() {
        System.out.println(_producer + "디스플레이 테스트");
    }
    public Display Clone() {
        return new Display(this);
    }

    public void Initialize(String producer) {
        _producer = producer;
    }

    protected String _producer;
}

InnerDevice.java

더보기
public class InnerDevice {
    public InnerDevice() {
        _producer = "기본";
    }

    public InnerDevice(InnerDevice other) {
        _producer = other._producer;
    }

    public void testKeyboardInput() {
        System.out.println(_producer + "키보드 입력 테스트");
    }

    public void testMicrophoneInput() {
        System.out.println(_producer + "마이크 입력 테스트");
    }

    public void Initialize(String producer) {
        _producer = producer;
    }

    public InnerDevice Clone() {
        return new InnerDevice(this);
    }

    private String _producer;
}

Phone.java

더보기
public class Phone {
    private Display _display;
    private InnerDevice _innerDevice;

    public Phone() {
        _display = new Display();
        _innerDevice = new InnerDevice();
    }
    public Phone(Phone other) {
        _display = other._display;
        _innerDevice = other._innerDevice;
    }
    public void Initialize(InnerDevice i, Display d) {
        _display = d;
        _innerDevice = i;
    }
    public Display getDisplay() {
        return _display;
    }
    public InnerDevice getInnerDevice() {
        return _innerDevice;
    }
    public Phone Clone() {
        return new Phone(this);
    }
}

PhonePrototypeFactory.java

더보기
public class PhonePrototypeFactory {
    PhonePrototypeFactory(Phone p, InnerDevice i_d, Display d) {
        _prototypePhone = p;
        _prototypeInnerDevice = i_d;
        _prototypeDisplay = d;
    }

    public Phone makePhone() {
        return _prototypePhone.Clone();
    }
    public InnerDevice makeInnerDevice(String producer) {
        InnerDevice innerDevice = _prototypeInnerDevice.Clone();
        innerDevice.Initialize(producer);
        return innerDevice;
    }
    public Display makeDisplay(String producer) {
        Display display = _prototypeDisplay.Clone();
        display.Initialize(producer);
        return display;
    }

    private Phone _prototypePhone;
    private InnerDevice _prototypeInnerDevice;
    private Display _prototypeDisplay;
}

TestPrototypePattern.java

더보기
public class TestPrototypePattern {
    public static void main(String[] args) {
        PhonePrototypeFactory phonePrototypeFactory = new PhonePrototypeFactory(
                new Phone(),
                new InnerDevice(),
                new Display()
        );
        Phone sPhone = CreateSamsungPhone(phonePrototypeFactory);
        Phone iPhone = CreateApplePhone(phonePrototypeFactory);

        System.out.println("\n- 삼성 -");
        sPhone.getDisplay().testDisplay();
        sPhone.getInnerDevice().testMicrophoneInput();
        sPhone.getInnerDevice().testKeyboardInput();
        System.out.println("\n- 애플 -");
        iPhone.getDisplay().testDisplay();
        iPhone.getInnerDevice().testMicrophoneInput();
        iPhone.getInnerDevice().testKeyboardInput();

    }

    public static Phone CreateSamsungPhone(PhonePrototypeFactory phonePrototypeFactory) {
        Phone phone = phonePrototypeFactory.makePhone();
        phone.Initialize(
                phonePrototypeFactory.makeInnerDevice("삼성"),
                phonePrototypeFactory.makeDisplay("삼성")
        );
        return phone;
    }
    public static Phone CreateApplePhone(PhonePrototypeFactory phonePrototypeFactory) {
        Phone phone = phonePrototypeFactory.makePhone();
        phone.Initialize(
                phonePrototypeFactory.makeInnerDevice("애플"),
                phonePrototypeFactory.makeDisplay("애플")
        );
        return phone;
    }
}

 

실행결과

마무리

 추상 팩토리 패턴과 어떤 면에서 경쟁적인 관계이다. 하지만 함께 사용될 수도 있다. 예를 들어 추상 팩토리 패턴은 원형 집합을 저장하다 필요할 때 복제하여 객체를 반환하도록 사용할 수 있다.

 

 복합체 패턴과 장식자 패턴을 많이 사용해야 하는 설계에서 사용하면 좋을 것이다.

 

 

참고

에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴』, 프로텍 미디어, 2015