디자인 패턴(구)/생성 패턴

프로토타입(Prototype) 패턴이란?

공대키메라 2022. 4. 6. 16:26

1. 프로토 타입 패턴이란?

의도

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

 

즉, 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법을 말한다. 

 

사용 시기

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

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

구조

 

구조에서 보듯이 원형에서 다양한 클래스가 파생해서 그 파생된 클래스를 사용하는 것이다. 

(이정도야뭐...)

 

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

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

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

 

2. 구현

 

PrototypeRespository.java

package me.whiteship.designpatterns._01_creational_patterns._05_prototype._my_code_before;

public class PrototypeRepository {

    private String user;

    private String name;

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

PrototypeIssue.java

package me.whiteship.designpatterns._01_creational_patterns._05_prototype._my_code_before;

import java.util.Objects;

public class PrototypeIssue implements Cloneable{

    private int id;

    private String title;

    private PrototypeRepository repository;

    public PrototypeIssue(PrototypeRepository repository) {
        this.repository = repository;
    }

   //getter & setter 선언 - setRepository는 X
   
   
    public PrototypeRepository getRepository() {
        return repository;
    }

    public String getText(){
        return String.format("Prototype Method Test with PrototypeRepository => "
                + repository.getUser() + " " + repository.getName() + " " + this.getId());
    }

//해당 클래스에서 Cloneable을 상속해서 clone 메소드 오버라이드
    //깊은 복사를 통해서 원래 프로토타입과 다른 인스턴스를 생성
    @Override
    protected Object clone() throws CloneNotSupportedException {
        PrototypeRepository repository = new PrototypeRepository();
        repository.setUser(this.repository.getUser());
        repository.setName(this.repository.getName());

        PrototypeIssue prototypeIssue = new PrototypeIssue(repository);
        prototypeIssue.setId(this.id);
        prototypeIssue.setTitle(this.getTitle());

        return prototypeIssue;
        //기본 clone을 사용하면 얕은 복사를 지원
//        return super.clone();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PrototypeIssue that = (PrototypeIssue) o;
        return id == that.id && Objects.equals(title, that.title) && Objects.equals(repository, that.repository);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title, repository);
    }
}

 

TestCode.java

package me.whiteship.designpatterns._01_creational_patterns._05_prototype._my_code_before;

public class TestCode {

    public static void main(String[] args) throws CloneNotSupportedException {
        PrototypeRepository repository = new PrototypeRepository();
        repository.setUser("키메라");
        repository.setName("어흥!");

        PrototypeIssue prototypeIssue = new PrototypeIssue(repository);
        prototypeIssue.setId(1);
        prototypeIssue.setTitle("아 무료하다... 생각보다 내용이 많네...");

        String text = prototypeIssue.getText();
        System.out.println("text = " + text);

        PrototypeIssue clone = (PrototypeIssue) prototypeIssue.clone();
        System.out.println("text = " + clone.getText());

        repository.setUser("이름 변환하기");

        System.out.println(clone != prototypeIssue);
        System.out.println(clone.equals(prototypeIssue));
        System.out.println(clone.getClass() == prototypeIssue.getClass());
        System.out.println(clone.getRepository() == prototypeIssue.getRepository());

        System.out.println(clone.getText());
    }
}

 

이번 패턴은 매우 간단해서 before 와 after를 비교하지는 않겠다.

 

기존 인스턴스를 복제하여 새로운 인스턴스를 만드는데, 이것으로 얻을 수 있는 장단점은 무엇일까?

장점

  • 복잡한 객체를 만드는 과정을 숨길 수 있다.
  • 그존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보디 비용적인 면에서 효율적일 수 있다.
  • 추상적인 타입을 리턴할 수 있다.

단점

  • 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. 

 

그러면 다시 이 프로토타입 패턴의 의도에 대해 생각해보자. 

 

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

 

 

우리 코드에서 원형이 되는 classs는 interface는 아니지만 PrototypeIssue라고 볼 수 있다.

 

그리고 그것을 clone 메소드를 Cloneable을 상속받아 구현한 결과 새로운 PrototypeIssue를 생성한 것을 확인할 수 있었다.

 

client에서는 원하면 이것을 clone해서 새롭게 만들어서 사용하면 된다. 

 


 

이번 내용은 다른 패턴에 비해 크게 어렵지가 않았다.

 

다만 깊은 복사와 얕은 복사에 대한 개념과 이 clone기응을 잘 알고 있어야만 구현이 쉬울 것 같다. 

 

이번 기회에 얕은 복사와 깊은 복사의 차이점을 java 에서 나중에 정리하도록 하겠다. 

 

결론적으로 우리는 의도를 파악할 수 있었다. 

 

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

 

원형을 바탕으로 견본을 복사해서 새로운 객체를 생성하였다!