핵심 인사이트 (3줄 요약)
- 본질: Prototype은 이미 준비된 객체를 복제해 생성 비용을 줄이는 패턴이고, Flyweight는 공유 가능한 상태를 묶어 대량 객체의 상주 메모리를 줄이는 패턴이다.
- 가치: 둘 다 "효율화"라는 말을 쓰지만 Prototype이 아끼는 것은 객체의 탄생 시간이고, Flyweight가 아끼는 것은 객체들이 동시에 존재할 때 차지하는 공간이다.
- 판단 포인트: 병목이 초기화 시간인지, 힙 사용량인지, 혹은 둘 다인지 먼저 구분해야 하며, 잘못 고르면 Prototype은 깊은 복사 비용 폭증으로, Flyweight는 공유 상태 버그로 되돌아온다.
Ⅰ. 개요 및 필요성
객체 성능 문제는 흔히 한 단어로 "생성 비용"이라고 묶지만, 실제로는 두 종류가 섞여 있다. 하나는 객체 하나를 만드는 데 데이터 로딩·파싱·복잡한 그래프 구성처럼 시간이 오래 걸리는 경우이고, 다른 하나는 비슷한 객체가 수만 개 동시에 존재해 메모리와 GC (Garbage Collection) 부담이 커지는 경우다. 이 둘을 같은 방식으로 해결하려 하면 대개 과설계나 역효과가 난다.
Prototype은 첫 번째 문제에 답한다. 비싼 초기화를 한 번 수행한 원형 객체를 준비해 두고, 필요할 때 복제해서 조금씩 수정하는 방식이다. 반면 Flyweight는 두 번째 문제에 답한다. 모든 객체가 따로 들고 있을 필요가 없는 공통 상태를 외부에서 공유하게 만들어, 개별 인스턴스의 무게를 최소화한다.
또 하나 중요한 점은 분류다. Prototype은 GoF (Gang of Four) 생성 패턴이고, Flyweight는 구조 패턴이다. 그럼에도 둘이 자주 같이 비교되는 이유는, 아키텍트가 실무에서 느끼는 질문이 "패턴 분류"보다 "메모리와 성능을 어떻게 아낄 것인가"이기 때문이다.
┌──────────────────────────────────────────────────────────────────────┐
│ Which cost hurts more? │
├──────────────────────────────────────────────────────────────────────┤
│ new() itself expensive? ──▶ Prototype │
│ many similar objects coexist? ──▶ Flyweight │
│ both are true? ──▶ combine clone + shared state │
└──────────────────────────────────────────────────────────────────────┘
즉 두 패턴의 출발점은 "효율화"라는 공통 목표가 아니라, 시간 최적화와 공간 최적화라는 서로 다른 질문이다. 이 경계를 먼저 잡아야 올바른 설계가 시작된다.
- 📢 섹션 요약 비유: Prototype은 비싼 맞춤 정장을 한 벌 만들어 두고 치수만 조금 바꿔 빨리 복제하는 방식이고, Flyweight는 옷걸이와 진열대를 공유해 수많은 옷을 더 적은 공간에 보관하는 방식이다.
Ⅱ. 아키텍처 및 핵심 원리
Prototype의 핵심 메커니즘은 "초기화 비용을 앞당겨 치르고, 이후에는 복사 비용으로 바꾸는 것"이다. 보통 Prototype Registry에 원형 객체를 보관해 두고, 클라이언트가 clone() 또는 복사 생성자를 통해 새 객체를 만든다. 이때 가장 중요한 설계 포인트는 얕은 복사 (Shallow Copy)와 깊은 복사 (Deep Copy)를 어디까지 허용할지다. 내부 그래프가 mutable하면 얕은 복사는 쉽게 공유 버그를 만든다.
Flyweight의 핵심 메커니즘은 "상태를 내재 상태와 외재 상태로 나누는 것"이다. 내재 상태 (Intrinsic State)는 여러 객체가 공통으로 가질 수 있고 보통 불변이어야 한다. 외재 상태 (Extrinsic State)는 위치, 시간, 사용자별 값처럼 호출 시점마다 달라지는 정보이며, 클라이언트가 별도로 들고 전달한다. 이 분리가 성공하면 메모리는 공유 상태 1개 + 외재 상태 × N 구조로 줄어든다.
┌──────────────────────────── Prototype ───────────────────────────────┐
│ Prototype Registry │
│ └─ prepared object │
│ │ │
│ ├─ shallow copy -> 빠르지만 내부 참조 공유 가능 │
│ └─ deep copy -> 안전하지만 복사 비용 증가 │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────── Flyweight ───────────────────────────────┐
│ Flyweight Factory │
│ └─ shared intrinsic state cache │
│ │ │
│ ├─ glyph A, font 12, black -> 1회만 저장 │
│ └─ clients pass position / order / runtime state │
└──────────────────────────────────────────────────────────────────────┘
| 구분 | Prototype | Flyweight |
|---|---|---|
| 절감 대상 | 생성 시간 | 상주 메모리 |
| 핵심 동작 | 원형 복제 | 공유 객체 재사용 |
| 설계 포인트 | 복사 범위와 독립성 보장 | 내재/외재 상태 분리 |
| 실패 원인 | 얕은 복사로 인한 참조 공유 버그 | mutable 공유 상태, 과도한 외재 상태 |
예를 들어 CAD (Computer-Aided Design) 모델처럼 로딩이 무거운 객체는 Prototype의 이점을 크게 본다. 반대로 텍스트 에디터의 글자 glyph, 게임 탄환 스프라이트, JVM (Java Virtual Machine)의 String Pool처럼 수많은 유사 객체가 동시에 존재하는 경우는 Flyweight가 더 직접적이다. 즉 구조는 달라도 둘 다 자원 사용의 "분모"를 줄이는 패턴이라는 점에서 효율화 논의에 함께 등장한다.
- 📢 섹션 요약 비유: Prototype은 마스터 반죽을 만들어 쿠키를 빠르게 찍어내는 것이고, Flyweight는 같은 쿠키 틀을 모두가 함께 쓰고 각자 초콜릿 장식만 따로 얹는 방식이다.
Ⅲ. 비교 및 연결
Prototype과 Flyweight를 구분하는 가장 좋은 질문은 "객체가 태어날 때 비싼가, 아니면 많이 살아 있을 때 비싼가"다. Prototype은 객체 수가 늘어도 각 인스턴스가 독립된 객체라는 사실을 바꾸지 않는다. 반면 Flyweight는 인스턴스 개념 자체를 가볍게 만들어, 동일한 내부 데이터를 공유하는 쪽으로 설계를 바꾼다.
| 비교 축 | Prototype | Flyweight | Object Pool |
|---|---|---|---|
| 주 목적 | 생성 시간 절감 | 메모리 절감 | 재사용 가능한 객체 회전 |
| 인스턴스 수 | 복제할수록 증가 | 공유 가능한 부분은 감소 | 제한된 수를 반복 사용 |
| 상태 처리 | 복제 후 독립 상태가 일반적 | 공유 상태는 불변, 개별 상태는 외부 보관 | 사용 전 초기화·반납 필요 |
| 대표 예 | 복잡한 설정 객체, 템플릿 문서 | String Pool, glyph cache, icon cache | DB connection, thread pool |
이 비교가 중요한 이유는 세 패턴이 모두 "낭비를 줄인다"는 느낌을 주기 때문이다. 그러나 Flyweight는 Object Pool처럼 객체를 회수해 돌려 쓰는 전략이 아니고, Prototype처럼 객체를 복제해 증가시키는 전략도 아니다. Flyweight는 공통 본체를 하나만 두고 바라보는 관점이고, Prototype은 원형에서 새 사본을 만드는 관점이다.
또한 두 패턴은 함께 쓰일 수 있다. 예를 들어 게임 맵 편집기에서 기본 맵 템플릿은 Prototype으로 빠르게 복제하고, 맵 안의 타일 텍스처와 아이콘은 Flyweight로 공유할 수 있다. 그래서 실무 설계에서는 둘 중 하나만 고집하기보다, 병목이 여러 층에 있으면 생성 비용과 상주 비용을 분리해 동시에 최적화하는 경우가 많다.
- 📢 섹션 요약 비유: Prototype은 원본 문서를 복사해 각자 한 부씩 나눠 갖는 방식이고, Flyweight는 도서관 참고서를 한 권만 두고 모두가 필요한 페이지 정보만 따로 적어 가는 방식이다.
Ⅳ. 실무 적용 및 기술사 판단
실무 판단은 숫자로 보는 것이 가장 명확하다. 객체 생성 한 번에 500ms가 걸리고 같은 템플릿을 수백 번 만들어야 한다면 Prototype이 유리하다. 반대로 100,000개의 총알 객체가 동시에 떠 있고 그중 90%가 같은 이미지와 공격 속성을 공유한다면, Flyweight가 메모리와 GC 부담을 훨씬 크게 줄인다.
실무 시나리오
| 상황 | 더 적합한 선택 | 이유 |
|---|---|---|
| 복잡한 보고서 템플릿, 3D 모델, 대형 설정 객체 복제 | Prototype | 비싼 초기화를 복제로 대체 |
| 문서 편집기의 문자 glyph, 게임 탄환, 아이콘 캐시 | Flyweight | 공통 데이터를 공유해 상주 메모리 절감 |
| 복잡한 템플릿 안에 반복 부품도 많은 경우 | 병행 적용 | 큰 틀은 복제하고 세부 부품은 공유 |
수치 예시
Bullet 100,000개 동시 존재 가정
[일반 객체]
스프라이트 512B + 공격력 8B + 위치/속도 24B = 544B
544B × 100,000 ≒ 54.4MB
[Flyweight 적용]
공유 내재 상태: 스프라이트 512B + 공격력 8B = 520B
개별 외재 상태: 위치/속도 24B
520B + (24B × 100,000) ≒ 2.4MB
설계 체크리스트
- 생성자 호출보다 복사 비용이 실제로 더 싼가?
- Prototype 대상 객체 안에 mutable 참조 그래프가 있는가?
- Flyweight에서 공유할 내재 상태를 불변으로 만들 수 있는가?
- 외재 상태가 너무 커서 공유 이점을 갉아먹지 않는가?
- 캐시 수명주기와 해제 전략을 설계했는가?
자주 발생하는 안티패턴
- 깊은 복사가 필요한 객체에 얕은 복사를 적용해 원본과 사본이 함께 깨지는 것
- Flyweight 객체를 mutable하게 만들어 공유 인스턴스 전체가 오염되는 것
- 메모리 문제인데 Prototype만 도입해 인스턴스 수를 더 늘리는 것
- 외재 상태 전달 비용이 큰데도 무조건 Flyweight를 적용하는 것
기술사 답안에서는 "Prototype vs Flyweight"를 단순 암기표로 쓰기보다, 시간 최적화냐 공간 최적화냐라는 질문으로 재구성해 설명하는 것이 좋다. 그래야 패턴 선택 기준과 실패 시나리오까지 설계 논리로 이어진다.
- 📢 섹션 요약 비유: 새 가게를 빨리 열고 싶으면 인테리어 설계를 복제하는 것이 중요하고, 창고가 부족하면 선반과 포장재를 공유하는 것이 중요하다. 같은 효율화라도 먼저 아픈 곳이 어디인지 봐야 한다.
Ⅴ. 기대효과 및 결론
Prototype과 Flyweight를 올바르게 구분하면 객체 설계의 성능 문제를 더 정확히 다룰 수 있다. Prototype은 비싼 초기화 비용을 앞단에서 흡수해 응답성을 높이고, Flyweight는 대량 객체가 차지하는 메모리와 GC 압력을 줄여 시스템 밀도를 높인다. 이 둘을 구분하지 못하면 "효율화"라는 이름으로 반대로 비용을 키우는 설계를 하기 쉽다.
한계도 분명하다. Prototype은 복제 전략이 잘못되면 독립성이 깨지고, Flyweight는 공유 상태를 안전하게 관리하지 못하면 전체 시스템에 버그가 번진다. 따라서 기억해야 할 핵심은 두 패턴을 같은 카테고리로 외우는 것이 아니라, Prototype은 객체의 탄생을 최적화하고 Flyweight는 객체들의 공존을 최적화한다는 관점이다.
- 📢 섹션 요약 비유: Prototype은 "빨리 새것을 만드는 기술"이고, Flyweight는 "많은 것을 함께 살아 있게 만드는 기술"이다. 둘 다 효율적이지만, 아끼는 자원과 설계 부담이 다르다.
📌 관련 개념 맵
| 개념 | 연결 포인트 |
|---|---|
| Shallow Copy | Prototype에서 빠르지만 참조 공유 위험이 있는 복사 방식이다. |
| Deep Copy | Prototype에서 독립성을 높이지만 비용이 큰 복사 방식이다. |
| Intrinsic State | Flyweight가 공유하는 불변 내부 상태다. |
| Extrinsic State | Flyweight 사용 시 호출자가 따로 들고 다니는 상태다. |
| Object Pool | 재사용 관점의 최적화 패턴으로 Flyweight와 다르다. |
| Builder | 복잡한 생성 과정을 제어하는 패턴으로 Prototype과 조합될 수 있다. |
| String Pool | Flyweight 개념이 언어 런타임에 적용된 대표 사례다. |
📈 관련 키워드 및 발전 흐름도
객체 성능 문제
│
├─ 생성 시점이 비싸다 ──▶ Prototype
│ │
│ └─ shallow copy / deep copy 판단
│
└─ 동시 상주 수가 많다 ──▶ Flyweight
│
└─ intrinsic / extrinsic state 분리
│
▼
복제 최적화 + 공유 최적화의 병행 설계
이 흐름은 객체 효율화 논의가 패턴 이름 암기보다, 비용이 생기는 시점을 먼저 가르는 판단 문제임을 보여 준다.
👶 어린이를 위한 3줄 비유 설명
- Prototype은 이미 만든 장난감을 본떠서 새 장난감을 빨리 하나 더 만드는 방법이에요.
- Flyweight는 여러 장난감이 똑같이 쓰는 큰 부품은 함께 쓰고, 이름표 같은 작은 부분만 각자 다르게 들고 있는 방법이에요.
- 그래서 Prototype은 빨리 만들 때 좋고, Flyweight는 많이 모아 둘 때 좋답니다.