디자인 패턴

프로토타입 (Prototype) 패턴

개발정리 2022. 6. 29. 22:03

기존의 객체를 응용해서 새로운 인스턴스를 만들때 사용된다.

네트워크를 거치거나 db 를 거쳐서 만들어야 하는 등의 복잡한 과정을 거쳐 인스턴스를 만드는 경우 리소스가 많이 드는데, 이미 만들어진 객체를 가지고 복제를 해서 새로운 인스턴스를 만들면 비용을 줄일 수 있다. 

 

아래 그림과 같이 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만들 수 있다.

 

백기선 - 디자인패턴 강의

 

프로토타입 패턴 실습


GithubRepository

public class GithubRepository {
  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;
  }
}

 

GithubIssue

public class GithubIssue implements Cloneable{

  private int id;
  private String title;

  private String url;

  public String getUrl() {
    return String.format("https://github.com/%s/%s/issues/%d",
        repository.getUser(),
        repository.getName(),
        this.getId());
  }

  public void setUrl(String url) {

    this.url = url;
  }

  private GithubRepository repository;

  public GithubIssue(GithubRepository repository) {
    this.repository = repository;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public GithubRepository getRepository() {
    return repository;
  }

  public void setRepository(GithubRepository repository) {
    this.repository = repository;
  }

  @Override
  // 얕은복사
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }

  // 깊은복사
  protected Object clone2() throws CloneNotSupportedException {
    GithubRepository repository = new GithubRepository();
    repository.setUser(this.repository.getUser());
    repository.setName(this.repository.getName());

    GithubIssue githubIssue = new GithubIssue(repository);
    githubIssue.setId(this.id);
    githubIssue.setTitle(this.title);

    return githubIssue;
  }

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

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

 

App

public class App {
  public static void main(String[] args) throws CloneNotSupportedException {

    GithubRepository repository = new GithubRepository();
    repository.setUser("whiteship");
    repository.setName("live-study");

    GithubIssue githubIssue = new GithubIssue(repository);
    githubIssue.setId(1);
    githubIssue.setTitle("1주차 과체: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가");

    String url = githubIssue.getUrl();
    System.out.println(url);

    GithubIssue clone = (GithubIssue) githubIssue.clone();
    System.out.println(clone != githubIssue); //새로운 인스턴스이기 때문에 다르다.
    System.out.println(clone.equals(githubIssue)); //세팅되어져 있는 데이터는 같을 수 있다.
    System.out.println(clone.getClass() == githubIssue.getClass());
    System.out.println(clone.getRepository() == githubIssue.getRepository()); //얕은복사
    System.out.println(clone.getUrl());
  }
}
//결과
https://github.com/whiteship/live-study/issues/1
true
true
true
true
https://github.com/whiteship/live-study/issues/1

 

위 코드처럼 자바는 인스턴스를 복제해주는 기본적인 메커니즘을 제공하는데 이 기능을 사용하여 프로토타입 패턴을 구현할 수 있다.

이를 사용하려면 Object 클래스의 clone 메서드를 사용해야한다. 하지만 protected 로 설정되어 있어 이 부분을 사용가능하도록 명시적으로 바꿔줘야하고 프로토타입 패턴을 적용할 클래스에 Cloneable 를 상속받아 Clone 메서드를 재정의하면 된다.

 

이때 생성자 파라미터로 참조객체가 넘어가면 해당 참조객체는 새로운 객체일까? 아니면 같은 주소값을 가진 객체일까?

public GithubIssue(GithubRepository repository) {
  this.repository = repository;
}

 

clone 메서드를 수정하지 않고 ide 에서 만들어준대로 재정의하였다면 얕은복사 즉, 같은 주소값을 가진 객체가 사용된다. 

// 얕은복사
@Override
protected Object clone() throws CloneNotSupportedException {
  return super.clone();
}

 

반대로 새로운 주소값을 가진 객체를 사용하고 싶다면 (깊은복사) 아래와 같이 clone 메서드를 구현하자.

// 깊은복사
protected Object clone() throws CloneNotSupportedException {
  GithubRepository repository = new GithubRepository();
  repository.setUser(this.repository.getUser());
  repository.setName(this.repository.getName());

  GithubIssue githubIssue = new GithubIssue(repository);
  githubIssue.setId(this.id);
  githubIssue.setTitle(this.title);

  return githubIssue;
}

 

 

 

장점과 단점


장점

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

 

단점

  • 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)

 

 

 

참고


인프런 - 백기선, 디자인패턴 강의