1996년 영국에서 돌리 양이 탄생했다. 이克隆 사건은 기술적으로 매우 흥미로운데, 바로 프로토타입 패턴의 개념과 깊이 연관되어 있기 때문이다.
1. 프로토타입 패턴이란?
프로토타입 패턴은 기존에 존재하는 객체를 복제하여 새로운 객체를 생성하는 디자인 패턴이다. 쉽게 말하면 복사-붙여넣기의 원리와 같다.
만약 어떤 객체를 생성하기 위해 복잡한 과정이 필요하고 코드가 매우 많이 필요하다고 가정해보자. 동일한 객체를 여러 번 생성해야 한다면 매번 동일한 복잡한 과정을 반복해야 할까? 이러한 문제를 해결하기 위해 프로토타입 패턴이 등장했다. 기존 객체를 템플릿으로 사용하여 복제함으로써 새로운 객체를 효율적으로 생성할 수 있다.
2. 프로토타입 패턴 구현 방법
실제 예제로 살펴보자. 정版 도서를 기준으로 복제된 도서를 생성하는 상황을 구현한다.
프로토타입 클래스 정의
프로토타입 클래스는 Cloneable 인터페이스를 구현해야 한다.
/**
* 프로토타입 클래스 (정版 도서)
*/
public class Novel implements Cloneable {
private String title;
private String writer;
private Date publicationDate;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Novel{" +
"title='" + title + '\'' +
", writer='" + writer + '\'' +
", publicationDate=" + publicationDate +
'}';
}
public Novel(String title, String writer, Date publicationDate) {
this.title = title;
this.writer = writer;
this.publicationDate = publicationDate;
}
public void setTitle(String title) {
this.title = title;
}
public void setWriter(String writer) {
this.writer = writer;
}
public void setPublicationDate(Date publicationDate) {
this.publicationDate = publicationDate;
}
}
테스트 클래스 정의
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Novel originalNovel = new Novel("优秀的Java编程", "李四", date);
Novel copiedNovel = (Novel) originalNovel.clone();
System.out.println("원본:" + originalNovel);
System.out.println("원본 해시코드:" + originalNovel.hashCode());
System.out.println("==================");
System.out.println("복제본:" + copiedNovel);
System.out.println("복제본 해시코드:" + copiedNovel.hashCode());
System.out.println("==================");
date.setTime(121212121);
System.out.println("날짜 변경 후 원본:" + originalNovel);
System.out.println("날짜 변경 후 복제본:" + copiedNovel);
}
}
테스트 결과 분석
위 결과를 보면, 원본 객체의 Date 값만 변경했는데 복제본 객체의 Date 값도 함께 변경되었다. 이를 **얕은 복제(Shallow Clone)**라고 한다.
반면, 원본 객체의 값을 변경해도 복제본 객체의 값이 처음 복제当时的 값을 유지하는 것을 **깊은 복제(Deep Clone)**라고 한다.
3. 깊은 복제 구현 방법
깊은 복제를 구현하는 방법은 두 가지가 있다.
3.1 clone() 메서드 오버라이드
clone() 메서드를 재정의하여 참조 변수도 함께 복제한다.
@Override
protected Object clone() throws CloneNotSupportedException {
Novel novel = (Novel) super.clone();
novel.publicationDate = (Date) this.publicationDate.clone();
return novel;
}
위 코드를 적용하면 원본과 복제본이 서로 독립적인 객체가 되어, 원본의 Date 값을 변경해도 복제본에는 영향을 주지 않는다.
3.2 직렬화와 역직렬화
Serializable 인터페이스를 구현하고 스트림을 통해 객체를 전송한다.
public class PrototypeTest {
public static void main(String[] args) throws Exception {
Date date = new Date();
Novel originalNovel = new Novel("水浒传", "施耐庵", date);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(originalNovel);
byte[] data = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
Novel copiedNovel = (Novel) ois.readObject();
System.out.println("원본:" + originalNovel);
System.out.println("복제본:" + copiedNovel);
System.out.println("===================");
date.setTime(123123123141L);
System.out.println("날짜 변경 후 원본:" + originalNovel);
System.out.println("날짜 변경 후 복제본:" + copiedNovel);
}
}
직렬화 방식은 IO 스트림을 사용해야 하므로 구현이 번거롭다. 따라서 깊은 복제가 필요한 경우 clone() 메서드 오버라이드 방식을 더 선호한다.
4. 얕은 복제와 깊은 복제의 차이점
얕은 복제: 기본 타입(primitive type) 값은 복사하지만, 참조 타입(reference type) 변수는 메모리 주소만 복사한다. 즉, 동일한 객체를 참조하게 된다.
깊은 복제: 기본 타입은 물론 참조 타입 객체까지 모두 복사하여 완전히 독립적인 두 객체를 생성한다.
이제 5가지 생성 패턴 중 프로토타입 패턴을 마무리하고, 다음으로는 구조형 패턴에 대해 살펴보도록 하자.