[디자인 패턴] (GoF 구조패턴) Flyweight Pattern에 대하여
안녕하세요! GoF 디자인 패턴 연재, 이번 시간에는 구조 패턴(Structural Patterns)의 한 종류로, 수많은 객체를 효율적으로 지원하여 메모리 사용량을 절약하는 플라이웨이트(Flyweight) 패턴에 대해 알아보겠습니다.
정의
플라이웨이트 패턴은 다수의 객체가 공유하는 공통된 상태(intrinsic state)를 분리하여, 여러 객체에서 이를 공유함으로써 메모리 사용량을 최소화하는 패턴입니다. 이름 그대로 객체를 '가볍게(Flyweight)' 만드는 것이 목적입니다.
거대한 숲을 게임으로 만든다고 상상해봅시다. 수백만 그루의 나무를 각각의 객체로 만든다면 엄청난 메모리가 필요할 겁니다. 하지만 자세히 보면, '소나무', '참나무' 등 나무의 종류는 몇 가지 안 됩니다. 나무의 모델, 텍스처, 색상 등은 모든 소나무에 동일하게 적용될 수 있습니다. 플라이웨이트 패턴은 바로 이 점에 착안합니다.
- 내부 상태 (Intrinsic State): 여러 객체가 공유할 수 있는, 변하지 않는 상태. (예: 나무의 종류, 색상, 텍스처)
- 외부 상태 (Extrinsic State): 각 객체마다 고유하며, 공유할 수 없는 상태. (예: 나무의 x, y 좌표, 크기)
패턴은 '내부 상태'를 가진 플라이웨이트 객체를 만들어 공유하고, 클라이언트는 이 공유 객체에 '외부 상태'를 결합하여 사용하는 방식으로 동작합니다. 이렇게 하면 수백만 그루의 나무를 표현하더라도, 실제 메모리에는 몇 종류의 나무 모델(플라이웨이트 객체)만 존재하게 되어 매우 효율적입니다.
플라이웨이트 패턴은 다음과 같이 구성됩니다.
- Flyweight (플라이웨이트): 공유 객체들이 구현해야 할 인터페이스를 정의합니다. 외부 상태를 매개변수로 받아 작업을 수행하는 메서드를 포함합니다.
- ConcreteFlyweight (구체적인 플라이웨이트): Flyweight 인터페이스를 구현하며, 공유될 내부 상태를 저장합니다.
- FlyweightFactory (플라이웨이트 팩토리): 플라이웨이트 객체의 생성과 관리를 담당합니다. 클라이언트가 요청한 플라이웨이트가 이미 존재하면 기존 객체를 반환하고, 없으면 새로 생성하여 저장소(pool)에 추가한 뒤 반환합니다.
- Client (클라이언트): 플라이웨이트 객체를 사용합니다. 외부 상태를 계산하거나 관리하며, 필요시 플라이웨이트 객체의 메서드에 이 외부 상태를 전달합니다.
사용예시
앞서 설명한 숲의 나무를 그리는 예제를 자바(Java) 코드로 구현해 보겠습니다.
1. 플라이웨이트 (Flyweight) 및 구체적인 플라이웨이트 (ConcreteFlyweight) 정의
공유할 나무의 종류(TreeType)를 정의합니다. 이것이 플라이웨이트 객체가 됩니다.
// Flyweight와 ConcreteFlyweight 역할을 동시에 수행
public class TreeType {
// 내부 상태 (Intrinsic State) - 공유됨
private final String name;
private final String color;
public TreeType(String name, String color) {
this.name = name;
this.color = color;
}
// 외부 상태를 받아 작업을 수행
public void draw(int x, int y) {
System.out.println(color + " " + name + " 나무를 (" + x + ", " + y + ") 위치에 그립니다.");
}
}
2. 플라이웨이트 팩토리 (FlyweightFactory) 정의
TreeType 객체들을 관리하고 공유하는 TreeFactory를 정의합니다.
import java.util.HashMap;
import java.util.Map;
// FlyweightFactory 클래스
public class TreeFactory {
// 플라이웨이트 객체들을 저장하는 캐시(풀)
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color) {
String key = name + "-" + color;
TreeType result = treeTypes.get(key);
// 캐시에 없으면 새로 생성하여 추가
if (result == null) {
result = new TreeType(name, color);
treeTypes.put(key, result);
System.out.println("새로운 플라이웨이트 생성: " + key);
}
return result;
}
}
3. 컨텍스트(Context) 클래스 및 클라이언트(Client) 코드
클라이언트는 Forest 클래스입니다. Forest는 각 나무의 외부 상태(좌표)를 관리하는 Tree 객체들을 가집니다.
import java.util.ArrayList;
import java.util.List;
// 외부 상태를 관리하는 Context 클래스
class Tree {
private int x;
private int y;
private TreeType type; // 플라이웨이트 객체에 대한 참조
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() {
type.draw(x, y);
}
}
// Client 클래스
public class Forest {
private List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, String name, String color) {
TreeType type = TreeFactory.getTreeType(name, color);
Tree tree = new Tree(x, y, type);
trees.add(tree);
}
public void draw() {
for (Tree tree : trees) {
tree.draw();
}
}
public static void main(String[] args) {
Forest forest = new Forest();
// 수많은 나무를 심는다.
forest.plantTree(10, 20, "소나무", "초록색");
forest.plantTree(50, 60, "단풍나무", "빨간색");
forest.plantTree(100, 120, "소나무", "초록색"); // 기존 객체 재사용
forest.plantTree(150, 160, "소나무", "초록색"); // 기존 객체 재사용
forest.draw();
// 실행 결과:
// 새로운 플라이웨이트 생성: 소나무-초록색
// 새로운 플라이웨이트 생성: 단풍나무-빨간색
// 초록색 소나무 나무를 (10, 20) 위치에 그립니다.
// 빨간색 단풍나무 나무를 (50, 60) 위치에 그립니다.
// 초록색 소나무 나무를 (100, 120) 위치에 그립니다.
// 초록색 소나무 나무를 (150, 160) 위치에 그립니다.
}
}
실행 결과를 보면, "소나무-초록색" 타입은 한 번만 생성되고 이후에는 계속 재사용되는 것을 확인할 수 있습니다. Hashmap에 같은 Key가 하나만 들어갈 수 있다는 구조를 이용한 것이죠!
결론
플라이웨이트는 다음의 경우에 사용됩니다.
- 애플리케이션이 대량의 객체를 사용해야 할 때
- 객체 생성으로 인해 메모리가 부족해지거나, 가비지 컬렉션으로 인한 성능 저하가 우려될 때
- 대부분의 객체 상태를 외부 상태로 분리하여 관리할 수 있을 때
- 객체의 정체성(identity)이 중요하지 않을 때 (공유되기 때문에 각 객체가 고유 ID를 갖기 어려움)
직관적이고 이해가 쉬운 구조를 가지고 있지만 다음과 같은 조건을 고려해야합니다
- 코드의 복잡성 증가: 내부/외부 상태를 분리하고 팩토리를 도입하는 과정에서 코드 구조가 복잡해질 수 있습니다.
- 런타임 비용: 팩토리에서 플라이웨이트 객체를 찾는 데 약간의 시간 비용이 발생할 수 있습니다.
플라이웨이트 패턴은 메모리 최적화가 중요한 시스템(게임, 대용량 문서 편집기 등)에서 매우 강력한 효과를 발휘합니다. 공유를 통해 자원을 극도로 아끼는 패턴이라고 기억해두시면 좋습니다. 다음 시간에는 Facade 패턴을 살펴보겠습니다