Flyweight
Flyweight는 객체들 간에 상태의 공통 부분을 공유하여 사용 가능한 RAM에 더 많은 객체를 담을 수 있게 하는 구조 디자인 패턴입니다.

Intent
Flyweight 패턴은 각 객체에 모든 데이터를 저장하는 대신, 여러 객체 간에 공유되는 상태를 추출하여 메모리 사용량을 줄입니다.
Problem
재미있는 비디오 게임을 만들었다고 가정합시다. 플레이어들이 지도를 돌아다니며 서로 총을 쏘는 게임입니다. 현실적인 파티클 시스템을 구현하여 총알, 미사일, 폭발 파편들이 화면을 가득 채웁니다.

게임이 끊임없이 충돌합니다. 수천 개의 파티클 객체 각각이 많은 데이터(좌표, 벡터, 속도, 색상, 스프라이트 등)를 포함하고 있어 사용 가능한 RAM이 부족해집니다.
Solution
Particle 클래스를 자세히 살펴보면, color와 sprite 필드가 다른 필드보다 훨씬 많은 메모리를 소비합니다. 더 나쁜 것은 이 두 필드가 모든 파티클에서 거의 동일한 데이터를 저장한다는 것입니다.

파티클의 다른 상태(좌표, 이동 벡터, 속도)는 각 파티클마다 고유합니다. 이 필드의 값은 시간에 따라 변합니다. 이 데이터는 파티클이 존재하는 항상 변화하는 컨텍스트를 나타내고, 색상과 스프라이트는 각 파티클에서 일정하게 유지됩니다.

객체의 이 일정한 데이터를 보통 **내재적 상태(intrinsic state)**라고 합니다. 이것은 객체 내부에 있으며, 다른 객체들은 이를 읽기만 하고 변경할 수는 없습니다.
객체의 나머지 상태는 종종 다른 객체들에 의해 “외부에서” 변경되며, 이를 **외재적 상태(extrinsic state)**라고 합니다.

Flyweight 패턴은 외재적 상태를 객체 내부에 저장하는 것을 중단하도록 제안합니다. 대신, 이 상태를 이에 의존하는 특정 메서드에 전달해야 합니다. 내재적 상태만 객체 내에 유지되어, 다른 컨텍스트에서 재사용할 수 있습니다.
Structure


| 구성 요소 | 역할 |
|---|---|
| Flyweight | 여러 객체 간에 공유될 수 있는 내재적 상태 부분을 포함 |
| Context | 외재적 상태를 포함하며, Flyweight 객체와 쌍을 이룸 |
| Flyweight Factory | 기존 flyweight 풀을 관리하고 재사용 또는 새 인스턴스 반환 |
| Client | flyweight의 외재적 상태를 계산하거나 저장 |
Pseudocode
나무 렌더링 최적화 예시:

// Flyweight: 공유되는 내재적 상태를 포함class TreeType { private name: string; private color: string; private texture: string;
constructor(name: string, color: string, texture: string) { this.name = name; this.color = color; this.texture = texture; }
draw(canvas: Canvas, x: number, y: number): void { // 1. 주어진 타입, 색상, 텍스처의 비트맵 생성 // 2. 캔버스의 X, Y 좌표에 비트맵 그리기 console.log(`Drawing ${this.name} tree at (${x}, ${y})`); }}
// Flyweight Factory: flyweight 풀 관리class TreeFactory { private static treeTypes: Map<string, TreeType> = new Map();
static getTreeType(name: string, color: string, texture: string): TreeType { const key = `${name}_${color}_${texture}`;
if (!this.treeTypes.has(key)) { this.treeTypes.set(key, new TreeType(name, color, texture)); console.log(`Created new TreeType: ${name}`); }
return this.treeTypes.get(key)!; }}
// Context: 외재적 상태와 flyweight 참조를 포함class Tree { private x: number; private y: number; private type: TreeType;
constructor(x: number, y: number, type: TreeType) { this.x = x; this.y = y; this.type = type; }
draw(canvas: Canvas): void { this.type.draw(canvas, this.x, this.y); }}
// Client: 숲 전체 관리class Forest { private trees: Tree[] = [];
plantTree(x: number, y: number, name: string, color: string, texture: string): void { const type = TreeFactory.getTreeType(name, color, texture); const tree = new Tree(x, y, type); this.trees.push(tree); }
draw(canvas: Canvas): void { for (const tree of this.trees) { tree.draw(canvas); } }}
// 사용 예시const forest = new Forest();
// 백만 그루의 나무를 심어도 TreeType은 몇 개만 생성됨for (let i = 0; i < 1000000; i++) { const x = Math.random() * 1000; const y = Math.random() * 1000;
// 무작위로 나무 타입 선택 const types = [ ["Oak", "green", "oak_texture"], ["Pine", "dark_green", "pine_texture"], ["Birch", "white", "birch_texture"] ]; const [name, color, texture] = types[Math.floor(Math.random() * 3)];
forest.plantTree(x, y, name, color, texture);}Applicability
다음의 경우에 패턴을 사용합니다:
수많은 유사 객체가 필요할 때
프로그램이 사용 가능한 RAM에 간신히 맞는 엄청난 수의 객체를 지원해야 할 때 사용합니다.
객체에 중복 상태가 있을 때
객체들이 추출하여 공유할 수 있는 중복 상태를 포함할 때 유용합니다.
How to Implement
-
상태 분류: flyweight가 될 클래스의 필드를 두 부분으로 나눕니다:
- 내재적 상태: 여러 객체에서 중복되는 불변 데이터
- 외재적 상태: 각 객체에 고유한 컨텍스트 데이터
-
내재적 필드 불변 유지: 클래스에 내재적 상태 필드를 남기되, 불변으로 만듭니다. 생성자에서만 초기 값을 받아야 합니다.
-
메서드 리팩토링: 외재적 상태 필드를 사용하는 메서드를 살펴봅니다. 메서드에서 사용되는 각 필드에 대해 새 매개변수를 도입하고 필드 대신 사용합니다.
-
Factory 생성: flyweight 풀을 관리하는 factory 클래스를 만듭니다. 새 flyweight 요청 시 기존 풀에서 찾거나 없으면 새로 생성합니다.
-
외재적 상태 관리: 클라이언트는 외재적 상태(컨텍스트)를 저장하거나 계산해야 합니다. 편의를 위해 flyweight 참조 필드와 함께 별도의 컨텍스트 클래스로 이동할 수 있습니다.
Pros and Cons
장점
| 장점 | 설명 |
|---|---|
| 메모리 절약 | 수많은 유사 객체가 있을 때 많은 RAM을 절약할 수 있습니다 |
단점
| 단점 | 설명 |
|---|---|
| CPU 사용량 증가 | 컨텍스트 데이터를 매번 재계산해야 하므로 CPU 사이클을 소비할 수 있습니다 |
| 코드 복잡성 | 코드가 훨씬 복잡해지며 팀원들이 이해하기 어려울 수 있습니다 |
Relations with Other Patterns
| 패턴 | 관계 |
|---|---|
| Composite | Composite 트리의 리프 노드를 Flyweight로 구현하여 RAM을 절약할 수 있습니다 |
| Facade | Flyweight는 많은 작은 객체를 만들고, Facade는 전체 서브시스템을 나타내는 하나의 객체를 만듭니다 |
| Singleton | Flyweight는 여러 인스턴스를 가질 수 있고 내재적 상태가 다릅니다. Singleton은 하나의 인스턴스만 있으며 가변 상태를 가질 수 있습니다 |