기존 인스턴스를 복제(clone) 해 새 객체를 만드는 패턴.
언제 쓰는가
- 생성 비용이 크다 (DB 조회·파일 파싱·복잡한 계산으로 초기화)
- 런타임에 결정되는 상태를 그대로 가진 사본이 필요하다
- 클래스를 모른 채 같은 종류의 객체를 만들어야 한다
interface Shape extends Cloneable { Shape copy(); }
class Circle implements Shape {
private int r; private Point center;
public Shape copy() { return new Circle(r, new Point(center)); } // 깊은 복사
}
얕은 복사가 함정이다
// Object.clone() 은 기본이 얕은 복사다
class Order implements Cloneable {
private List<Item> items;
public Order clone() throws CloneNotSupportedException {
return (Order) super.clone(); // ✗ items 는 '같은 리스트' 를 공유한다
}
}
- 사본에서 items.add(...) 하면 원본도 바뀐다
- 가변 참조 필드는 반드시 함께 복사해야 한다 (깊은 복사)
Cloneable의 문제
Cloneable 은 메서드가 없는 마커 인터페이스인데, clone() 은 Object 에 protected 로 있다 — 설계가 어긋나 있다
그래서 실무에서는
· 복사 생성자 Order(Order other)
· 복사 팩토리 static Order copyOf(Order other)
를 더 권장한다 (Effective Java Item 13)
실무에서
ArrayList.clone() · HashMap.clone() // 얕은 복사
Object.clone() 대신 List.copyOf(src) // 불변 사본
Spring @Scope("prototype") // 이름은 같지만 다른 개념(빈 스코프)
Spring의 prototype 스코프는 GoF Prototype과 무관하다 — 요청마다 새 인스턴스를 준다는 뜻이지 복제가 아니다. 면접에서 자주 섞인다.
면접 함정
- ❌
clone()이 항상 깊은 복사 → 기본은 얕은 복사다. - ❌ Spring prototype 스코프 = GoF Prototype → 이름만 같다.
복사 생성자를 권하는 이유
// clone() — 검사 예외 · 캐스팅 · protected 접근
Order copy = (Order) original.clone();
// 복사 생성자 — 타입 안전하고 상속에서도 명확하다
Order copy = new Order(original);
// 복사 팩토리 — 인터페이스 타입으로도 받는다
static Order copyOf(Order o) { ... }
불변이면 복제가 불필요하다
record Point(int x, int y) {}
Point p2 = p1; // 공유해도 안전
Point moved = new Point(p1.x()+10, p1.y()); // 일부만 바꾼 사본
불변성이 Prototype의 필요를 줄인다. 가변 객체를 다루기 때문에 복제가 필요한 것이다.
등록형 프로토타입
class ShapeRegistry {
private static final Map<String, Shape> prototypes =
Map.of("circle", new Circle(10), "square", new Square(5));
static Shape create(String key) { return prototypes.get(key).copy(); }
}
생성 비용이 큰 객체를 기동 시 한 번만 만들어 두고 이후엔 복제로 쓴다.