“코딩으로 학습하는 GoF의 디자인 패턴” 강의를 보며 간략하게 정리한 내용입니다.
프로토타입(Prototype) 패턴이란?
- 객체 생성을 위해 클래스에서 인스턴스를 생성하는 대신, 기존에 생성된 객체를 복제하여 새로운 객체를 생성하는 패턴
- Object 의 clone() 메서드를 통해 구현할 수 있다
clone() 조건
- x.clone() ≠ x // 다른 인스턴스가 새로 만들어진거라 같지 않음
- x.clone().getClass() == x.getClass()
- x.clone().equals(x) // 데이터는 같기 때문에 true
Cloneable 인터페이스를 구현해야함
프로토타입 패턴을 사용하는 이유
- 동일한 작업을 반복하지 않아도 됨(리소스 소모가 심한 작업일 경우 복제해 사용하면 이득을 볼 수 있음)
- DB에서 데이터를 가져와서 객체를 만들어야 하는 상황일때 등
예제 코드
public class GithubIssue implements Cloneable {
private int id;
private String title;
private GithubRepository repository;
public GithubIssue(GithubRepository repository) {
this.repository = repository;
}
...getter/setter 생략
}
public class GithubRepository {
private String user;
private String name;
...getter/setter 생략
}
먼저 유저의 이름과 리포지토리의 이름 정보를 가지고 있는 GithubRepository 클래스를 정의, 아이디와 제목과 이슈가 속한 리포지토리 정보를 가지고 있는 클래스가 있다.
이 클래스들을 사용해 url을 생성한다면 이런식으로 만들 수 있다.
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);
}
만약에 리포지토리는 같고 이슈만 다른 url 정보를 만들고 싶을 때 리포지토리의 정보를 세팅하는 과정을 모두 생략하고 이슈 정보만 바꿔야 할 때 clone()을 활용해서 간단하게 구현할 수 있다.
먼저 클론을 구현하고 싶은 클래스에 Cloneable 인터페이스의 clone()메서드를 구현해줘야 한다
public class GithubIssue implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
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());
}
이때 기본적으로 자바가 제공하는 super.clone() 기능은 얕은 복사를 사용한다. 그래서 객체 복사시에 같은 repository를 참조하게 된다.
기존 프로토타입에 변화를 받지 않는 깊은 복사를 구현하고 싶다면 clone() 메서드에 직접 구현 하면된다.
@Override
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;
}
정리
- 프로토타입 패턴을 구현하면 복잡한 객체를 만드는 과정을 감출 수 있다.
- 순환 참조가 있는 경우 복제한 객체를 만드는 과정 자체가 복잡할 수 있다.