기존의 객체를 응용해서 새로운 인스턴스를 만들때 사용된다.
네트워크를 거치거나 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;
}
장점과 단점
장점
- 복잡한 객체를 만드는 과정을 숨길 수 있다.
- 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 또는 메모리)적인 면에서 효율적일 수도 있다.
- 추상적인 타입을 리턴할 수 있다.
단점
- 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)
참고
인프런 - 백기선, 디자인패턴 강의
'디자인 패턴' 카테고리의 다른 글
브릿지 패턴 (Bridge Pattern) (0) | 2022.07.12 |
---|---|
어댑터 패턴 (Adapter Pattern) (0) | 2022.07.05 |
추상 팩토리 (Abstract factory) 패턴 (0) | 2022.06.19 |
팩토리 메소드 패턴 (Factory Method Pattern) (0) | 2022.06.14 |
싱글톤 패턴 (Singleton Pattern) (0) | 2022.06.04 |