복제의 예술
조금 철학적이면서 인문학적인 화두를 제시해 보겠습니다. 당신을 완벽히 복제한 객체는 당신인가요? 외모는 물론이고, 성격, 습관, 말투까지 완벽히 같다면 그 복제본의 정체성은 무엇일까요? 이런 주제로 만든 디스토피아적인 영화들이 많은데, 그중에서도 '아일랜드'가 대표적입니다. 이런 작품들은 종종 인간을 용도로만 바라보는 시각을 보여줍니다. 그렇다면 복제된 객체는 정말 인간일까요? 불가능한 일처럼 보일 수 있지만, 현재의 기술 발전 속도를 고려하면 먼 미래의 이야기만은 아닐 수도 있습니다.
1986년 David Ungar와 Randall Smith가 개발한 Self라는 언어에서는 클래스 없이 객체를 직접 복제하는 방식을 도입했습니다. 일반적으로 객체가 만들어지기 위해서는 앞서 살펴본 클래스가 기본이 되어야 합니다. 클래스에서 객체를 새로 생성하는 것이 표준적인 방식이었지만, Self 언어에서는 객체만으로도 복제가 가능하게 만들었습니다. 이 개념은 Smalltalk 언어의 객체 복제 메커니즘으로 발전하여 1994년 GoF의 책에서 '프로토타입 패턴'이라는 이름으로 공식화되었습니다. (Smalltalk 언어가 자주 언급되는 이유는 객체지향 프로그래밍의 선구자로서 현재 널리 사용되는 Java 언어의 시초이기 때문입니다.)
일반적으로 클래스를 기반으로 객체를 생성하고, 생성된 객체에 여러 설정을 적용합니다. 유사한 객체를 또 만들기 위해서는 다시 클래스에서 새 객체를 생성하고 같은 절차를 반복해야 하므로 비용이 발생합니다. 이미 구성된 객체를 그대로 복사해서 약간 다르게 활용할 수 있다면 더 효율적일 수 있습니다. 이는 마치 세포가 분열하는 것과 유사하다고 볼 수 있습니다.
// 프로토타입 인터페이스
public interface Prototype {
Prototype clone();
}
// 구체적인 프로토타입
public class Document implements Prototype {
private String content;
private String formatting;
private List<String> images;
// 생성자 및 다른 메서드들...
@Override
public Document clone() {
Document copy = new Document();
copy.content = this.content;
copy.formatting = this.formatting;
// 깊은 복제
copy.images = new ArrayList<>(this.images);
return copy;
}
}
// 최초 생성 객체
Document A_Doc = new Document();
Document B_Doc = A_Doc.clone(); // 복제한 객체
Document C_Doc = B_Doc.clone(); // 복제한 객체
프로토타입 패턴의 핵심은 복제(clone) 메서드입니다. 이 메서드는 객체 자신의 복사본을 생성해서 반환합니다. A_Doc 객체를 만들고 A_Doc 객체의 images를 추가한 후 B_Doc으로 복제하면 A_Doc에 있는 값들이 그대로 복사됩니다. B_Doc에 있는 content를 바꾸고, C_Doc으로 복제하면 B_Doc의 객체를 그대로 복제합니다. 다만, C_Doc은 A_Doc과 다른 값을 가지고 있게 됩니다. 이런 식으로 객체를 증식할 수 있습니다.
복제에는 얕은 복제(Shallow Copy)와 깊은 복제(Deep Copy) 두 가지 방식이 있습니다. 얕은 복제는 말 그대로 보이는 것만 복제한다는 의미입니다. 객체가 가지고 있는 속성값만 복제하고, 만약 속성 중에 다른 객체를 참조(reference)하고 있다면 참조만 하지, 참조 객체의 값까지 복제하지 않습니다. 이는, 원본이 참조하고 있는 객체의 값이 변경되면 복제된 것의 값도 변경된다는 의미입니다. 복제를 했지만, 연결되어 있는 것이죠.
반면, 깊은 복제는 참조한 객체와 객체에 설정한 값까지 모조리 복제합니다. 완전히 같은 하나를 더 만드는 겁니다. 위의 코드에서 String content는 일반 속성 값에 해당하지만, List<String> images는 ArrayList (List와 같고, 객체 생성 시 ArrayList 클래스로 생성합니다.) 객체를 참조하기에, 리스트 안에 있는 모든 값을 가져오기 위해서는 객체를 생성하면서 초기 image값들을 입력해 줍니다.
'프로토타입'이라는 용어는 그리스어 'prototypon'(원형, 선례)에서 유래했습니다. 이 용어는 제조업, 건축, 예술 등 다양한 분야에서 사용되며, 실제 제작 전에 만드는 시제품도 프로토타입이라고 부릅니다.
용어의 개념에 따라 프로그래밍도 객체의 원형과 선례(미리 초기화하거나, 복잡한 값들을 가지고 있는..)를 복제하여, 다르게 효율적으로 재사용할 수 있도록 생성해 주는 유연성 높은 강력한 도구입니다.
처음 화두에서 당신을 그대로 복제했다면 그건 당신인가요? 에서 프로그래밍에서는 당신이라고 말합니다. '또 다른 하나의 당신'이라는 표현이 맞을 듯하며, 사라지지 않는 분신술 같은 것입니다. 깊은 복제를 한다면 완전히 같은 독립적인 객체가 하나 더 생성된다는 의미가 됩니다. 다만, 분열하고 나서 사용되는 용도가 다르다면 점차 다른 객체가 될 수 있습니다.
복제는 단순히 재미로 하는 것이 아니라 필요에 의해 이루어집니다. 이미 복잡하게 구성된 것을 그대로 복제해 새로운 것을 만들 때 유용합니다. 마치 아이작 뉴턴이 "내가 더 멀리 볼 수 있었던 것은 거인의 어깨 위에 서 있었기 때문이다"라고 말했듯이, 누군가의 지식과 경험을 따라 하고 복제한 후 더 발전시키는 과정과도 같습니다.
프로그래밍도 마찬가지로, 이미 만들어진 수많은 객체를 적절하게 활용하고 복제하여 필요에 맞게 맞춤화하는 것이 효율적인 접근법입니다. 아무것도 없는 상태에서 라이브러리도 없이 처음부터 시작하는 것은 비효율적이고 시간 낭비일 수 있습니다. (그래서 코딩의 50%가 ctrl+c, ctrl+v 인지도 모르겠습니다.)