brunch

제11장: 플라이웨이트 패턴

공유를 통한 효율적인 객체 사용

by jeromeNa

서점에는 많은 책들이 있습니다. 이 중에서 같은 내용, 같은 저자, 같은 출판사의 책들도 많죠. 각각 한 권의 책들만 있으면 팔리고 나면 절판될 수 있죠. 동일한 책이 여러 권 있을 때, 각 책 사본마다 책의 모든 정보를 기록하는 건 자원낭비입니다. 10권의 같은 책이 있다면, 같은 정보를 10번 기록하는 것이죠. 하나의 정보만 기록하고 각권이 언제 팔렸지는 만 별도로 기록하면 더 효율적일 수 있습니다.


또 다른 예로 게임을 보죠. 화면에 보이는 모든 나무와 풀 각각에 대한 모델링, 텍스처, 형태 정보를 별도로 저장한다면, 1000개의 나무 모델 데이터를 1000번 중복되게 만들어야 하죠. 나무와 풀의 기본 모델과 텍스처는 유형별로 저장하고 이를 가져와 위치, 크기, 기울기 등은 별도로 저장하는 게 더 효율적입니다. 다시 말해, 하나의 기초 데이터만 만들고, 이를 참조해서 별도의 고유한 특성만 저장하는 게 낮다는 의미입니다.


예시에서 말한 것 중에 기본 데이터(책의 내용, 저자, 출판사, 게임의 나무 모델, 텍스처)는 공통적이고 변하지 않는 정보입니다. 이를 ‘내부(intrinsic) 상태’라고 부르고, 공유하지 않는 고유 정보 데이터(책의 위치, 판매일자, 게임의 모델 위치, 크기, 기울기)는 ‘외부(extrinsic) 상태’라고 부릅니다.




플라이웨이트(Flyweight)는 ‘가벼운 무게’라는 뜻으로, 복싱에서 가장 가벼운 체급을 의미합니다. 1980년대 중반에서 후반에 걸쳐 발전한 초기 GUI 시스템들 (예: Window System)에서는 그래픽 요소의 표현을 최적화하기 위해 이와 유사한 패턴을 사용하고 있었습니다. 특히 텍스트 편집기나 워드 프로세서에서 문자와 서식 정보를 처리할 때 메모리 사용을 줄이기 위한 방법을 활용되었습니다. 이를 GoF에 의해 공식적으로 플라이웨이트 패턴이라고 문서화되었습니다.


게임의 나무 생성을 간단한 예로 보겠습니다.


// 나무의 공유 가능한 데이터(내부 상태)를 담는 클래스 - 기초 데이터
class TreeType {
private String name; // 나무 종류(예: 소나무, 참나무 등)
private String texture; // 텍스처 이미지 경로
private String leafColor; // 잎 색상

public TreeType(String name, String texture, String leafColor) {
this.name = name;
this.texture = texture;
this.leafColor = leafColor;

// 나무 객체를 생성합니다.
System.out.println("TreeType 객체 생성: " + name + " (텍스처 로딩 중...)");
}

// 나무의 고유 특성에 따라 노출시킵니다.
public void render(int x, int y, int height, int width) {
System.out.println(name + " 나무를 위치 (" + x + ", " + y + ")에 " +
"높이 " + height + ", 너비 " + width + "로 그립니다. " +
"색상: " + leafColor);
}
}

// TreeType 객체를 관리하는 팩토리 클래스
class TreeFactory {
// 생성된 나무 객체를 관리합니다.
private static Map<String, TreeType> treeTypes = new HashMap<>();

public static TreeType getTreeType(String name, String texture, String leafColor) {
// 키 생성 (name, texture, leafColor 조합)
String key = name + "-" + texture + "-" + leafColor;

// 이미 생성된 트리 타입이 있으면 새로 만들지 않고 그걸 반환
if (!treeTypes.containsKey(key)) {
treeTypes.put(key, new TreeType(name, texture, leafColor));
}

return treeTypes.get(key);
}
// 생성된 나무 객체가 몇 개인지 확인합니다.
public static int getTreeTypeCount() {
return treeTypes.size();
}
}

// 실제 게임 월드에 배치되는 각 나무 인스턴스
// 이 클래스는 나무의 위치, 크기 등 외부 상태만 저장
class Tree {
private int x, y; // 위치 좌표
private int height, width; // 크기
private TreeType type; // 공유되는 나무 타입

public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;

// 나무마다 약간 다른 크기 설정 (변동성 주기)
this.height = 100 + (int)(Math.random() * 20);
this.width = 50 + (int)(Math.random() * 10);
}
// 나무를 그립니다.
public void draw() {
type.render(x, y, height, width);
}
}

// 사용 예시
public class ForestGame {
public static void main(String[] args) {
// 게임에 여러 종류의 나무 배치하기
List<Tree> trees = new ArrayList<>();

// 소나무 10그루
for (int i = 0; i < 10; i++) {
TreeType pineType = TreeFactory.getTreeType("소나무", "pine.png", "짙은 녹색");
trees.add(new Tree(random(0, 100), random(0, 100), pineType));
}

// 참나무 5그루
for (int i = 0; i < 5; i++) {
TreeType oakType = TreeFactory.getTreeType("참나무", "oak.png", "밝은 녹색");
trees.add(new Tree(random(0, 100), random(0, 100), oakType));
}

// 단풍나무 3그루 (가을 색상)
for (int i = 0; i < 3; i++) {
TreeType mapleType = TreeFactory.getTreeType("단풍나무", "maple.png", "빨간색");
trees.add(new Tree(random(0, 100), random(0, 100), mapleType));
}

// 모든 나무 그리기
System.out.println("\n=== 숲 렌더링 시작 ===");
for (Tree tree : trees) {
tree.draw();
}

// 결과 확인
System.out.println("\n총 나무 수: " + trees.size());
System.out.println("생성된 TreeType 객체 수: " + TreeFactory.getTreeTypeCount());
}

// 랜덤 위치 생성 헬퍼 메서드
private static int random(int min, int max) {
return min + (int)(Math.random() * (max - min));
}
}


예제에서 TreeType은 나무의 종류, 텍스처, 색상과 같은 공유 가능한 내부 상태를 저장합니다. Tree는 위치, 크기와 같은 각 나무마다 다른 외부 상태를 저장합니다. TreeFactory는 이미 생성된 TreeType 객체를 저장하고 재사용합니다. 예제에서 18개의 나무를 생성하지만, 실제 TreeType 객체는 3개만 만들어집니다.


플라이웨이트 패턴은 “적게 사용하고 많이 공유하라”는 원칙에 해당합니다. 유사한 객체들 간에 데이터를 공유하기에 메모리 사용량을 감소시키고, 객체 생성을 최대한 적게 함으로써 생성 비용을 절감할 수 있습니다. 대량의 유사 객체를 다룰 때 효과적으로 사용할 수 있는 패턴입니다.




최대한 적게 만들면서 많은 사람이 사용하고, 경험하게 하는 것이 공유경제입니다. 차량 렌트를 생각하면 렌트업체에서 자동차를 구매하고, 여러 사람에게 공유합니다. 공유되는 자동차의 내부 상태는 변하지 않습니다. 하지만, 자동차를 이용하는 많은 사용자에게는 고유한 경험을 제공합니다. 자동차라는 실제(TreeType이라는 개념)와 경험이라는 개념(Tree라는 실제)이 다를 뿐, 평면적으로 봤을 때는 같은 맥락으로 봐도 무관하지 않을까 합니다.




keyword