다수의 객체로 생성될 경우 모두가 갖는 본질적인 요소를 클래스화하여 공유함으로써 (여러개의 가상 인스턴스를 제공해 재사용함으로써) 메모리를 절약하고, 클래스의 경량화를 목적으로 하는 디자인 패턴이다.
간단히 말하면 캐시(Cache) 개념을 코드로 패턴화 한것으로 보면 되는데, 자주 변화는 속성(extrinsit)과 변하지 않는 속성(intrinsit)을 분리하고 변하지 않는 속성을 캐시하여 재사용해 메모리 사용을 줄이는 방식이다. 그래서 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 최적화를 노리는 경량 패턴이라고도 불린다.
intrinsic란 '고유한, 본질적인' 이라는 의미를 가진다. 본질적인 상태란 인스턴스가 어떠한 상황에서도 변하지 않는 정보를 말한다. 즉, 장소나 상황에 의존하지 않고 값이 고정되어 해당 객체나 인스턴스에 본질적으로 속한 상태이다. 그래서 값이 고정되어 있기에 충분히 언제 어디서 공유해도 문제가 없게 된다.
extrinsic이란 '외적인, 비본질적인' 이라는 의미를 가진다. 인스턴스를 두는 장소나 상황에 따라서 변화하는 정보를 말한다. 그래서 장소나 상황에 따라 값이 언제 어디서 변화할지 모르기 때문에 이를 캐시해서 공유할 수는 없다.
- Flyweight : 경량 객체를 묶는 인터페이스
- ConcreteFlyweight : 공유 가능하여 재사용되는 객체 (intrinsic state)
- UnsahredConcreteFlyweight : 공유 불가능한 객체 (extrinsic state)
- FlyweightFactory : 경량 객체 생성 역할과 캐시 역할을 겸비하는 Flyweight 객체 관리 클래스
만일 객체가 메모리에 존재하면 그대로 가져와 반환하고, 없다면 새로 생성해 반환한다
4-1. GetFlyweight() 메서드는 팩토리 메서드 역할을 한다고 보면 된다- Client : 클라이언트는 FlyweightFactory를 통해 Flyweight 타입의 객체를 얻어 사용한다
예를 들어 고전게임중 하늘에서 내리는 폭탄을 피하는 플래쉬 게임을 예로 들자면, 폭탄의 생김새나 성질은 모두 동일하나 하늘에서 내려오는 위치 좌표값만 다르기 때문에, 폭탄 하나하나를 new 연산자를 사용해서 인스턴스화 하는 것은 상당한 메모리를 차지하게 될것이다.
이 경우에 폭탄의 생김새와 폭탄의 성질같이 모든 폭탄이 공유하는 성질(Intrisic State)을 ConcreteFlyweight로 처리하고 (캐시처리하여 모든 폭탄이 공유하고) 폭탄의 좌표값과 같이 각 폭탄마다 다른 변화하는 성질을 UnsharedConcreteFlyweight가 처리하며 이 폭탄 객체를 FlyweightFactory가 생성하고 캐싱하고 관리하도록 구현하면 메모리 자원의 부담을 훨씬 줄일 수가 있다.
// Memory.java (기능 X, 메모리 사용량 기록 및 출력용)
public class Memory {
// 총 메모리 사용량
public static long size = 0;
public static void print() {
System.out.println("총 메모리 사용량 : " + Memory.size + "MB");
}
}
// Tree.java
public class Tree {
// 나무 종류
String type;
// 나무 색
String color;
// 위치 변수
double positionX;
double positionY;
public Tree(String type, String color, double positionX, double positionY) {
this.type = type;
this.color = color;
this.positionX = positionX;
this.positionY = positionY;
// 메모리 사용량 텍스트 길이로 계산
// 위치정보는 10으로 고정
int size = (type.length() * 10)
+ (color.length() * 10)
+ 10;
Memory.size += size;
}
}
// Territory.java
public class Territory {
static final int CANVAS_SIZE = 10000;
public void render(String type, String color) {
Tree tree = new Tree(
type,
color,
Math.random() * CANVAS_SIZE,
Math.random() * CANVAS_SIZE
);
System.out.println(tree.color + " 색상의 "
+ tree.type + " 나무 생성 완료("
+ tree.positionX + ", " + tree.positionY + ")");
}
}
// Client.java
public class Client {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Territory territory = new Territory();
for (int i = 0; i < 6; i++) {
System.out.println("나무의 종류를 입력해주세요.");
String type = sc.nextLine();
System.out.println("나무의 색상을 입력해주세요.");
String color = sc.nextLine();
territory.render(type, color);
}
sc.close();
Memory.print();
}
}
// 실행 결과
나무의 종류를 입력해주세요.
Oak
나무의 색상을 입력해주세요.
red
red 색상의 Oak 나무 생성 완료(5508.580893288153, 8205.70414872676)
나무의 종류를 입력해주세요.
Oak
나무의 색상을 입력해주세요.
red
red 색상의 Oak 나무 생성 완료(7333.885227249698, 5282.596159968424)
나무의 종류를 입력해주세요.
Oak
나무의 색상을 입력해주세요.
red
red 색상의 Oak 나무 생성 완료(941.7927506756763, 7634.92036767979)
나무의 종류를 입력해주세요.
Acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 Acacia 나무 생성 완료(2219.260125026495, 753.9244908752607)
나무의 종류를 입력해주세요.
Acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 Acacia 나무 생성 완료(2508.166617666886, 2369.477578763155)
나무의 종류를 입력해주세요.
Acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 Acacia 나무 생성 완료(9481.39537984539, 9134.761522195031)
총 메모리 사용량 : 570MB
red 색상의 Oak나무 3그루와 brown 색상의 Acacia 나무 3그루를 각각 심었을 때 총 570MB의 메모리 자원을 사용했다.
// Tree.java (UnsharedConcreteFlyweight)
public class Tree {
// 위치 변수의 메모리 사용량 = 10
long size = 10;
// 위치 변수
double positionX;
double positionY;
// 나무 모델
TreeModel model;
public Tree(TreeModel model, double positionX, double positionY) {
this.model = model;
this.positionX = positionX;
this.positionY = positionY;
Memory.size += this.size;
}
}
// TreeModel.java (ConcreteFlyweight)
public class TreeModel {
// 나무 종류
String type;
// 나무 색
String color;
public TreeModel(String type, String color) {
this.type = type;
this.color = color;
int size = (type.length() * 10) + (color.length() * 10);
Memory.size += size;
}
}
// TreeModelFactory.java (FlyweightFactory)
public class TreeModelFactory {
private static final Map<String, TreeModel> cache = new HashMap<>();
public static TreeModel getInstance(String type, String color) {
String key = type + color;
if (cache.containsKey(key)) {
return cache.get(key);
} else {
TreeModel model = new TreeModel(
type,
color
);
cache.put(key, model);
return model;
}
}
}
// Territory.java (Client)
public class Territory {
static final int CANVAS_SIZE = 10000;
public void render(String type, String color) {
TreeModel model = TreeModelFactory.getInstance(type, color);
Tree tree = new Tree(model, Math.random() * CANVAS_SIZE, Math.random() * CANVAS_SIZE);
System.out.println(tree.model.color + " 색상의 "
+ tree.model.type + " 나무 생성 완료("
+ tree.positionX + ", " + tree.positionY
+ ")" + " " + Memory.size);
}
}
// Client.java
public class Client {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Territory territory = new Territory();
for (int i = 0; i < 6; i++) {
System.out.println("나무의 종류를 입력해주세요.");
String type = sc.nextLine();
System.out.println("나무의 색상을 입력해주세요.");
String color = sc.nextLine();
territory.render(type, color);
}
sc.close();
Memory.print();
}
}
// 실행 결과
나무의 종류를 입력해주세요.
oak
나무의 색상을 입력해주세요.
red
red 색상의 oak 나무 생성 완료(481.94793999206564, 4349.290689489586)
나무의 종류를 입력해주세요.
oak
나무의 색상을 입력해주세요.
red
red 색상의 oak 나무 생성 완료(4546.057633983964, 1056.7455805318482)
나무의 종류를 입력해주세요.
oak
나무의 색상을 입력해주세요.
red
red 색상의 oak 나무 생성 완료(8690.249526023548, 5926.032790916922)
나무의 종류를 입력해주세요.
acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 acacia 나무 생성 완료(3955.509639560143, 7739.379100737045)
나무의 종류를 입력해주세요.
acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 acacia 나무 생성 완료(3178.3484382577653, 2855.9350805983663)
나무의 종류를 입력해주세요.
acacia
나무의 색상을 입력해주세요.
brown
brown 색상의 acacia 나무 생성 완료(3956.1896259885566, 7093.105061332645)
총 메모리 사용량 : 230MB
플라이웨이트 패턴을 적용하여 동일한 개수의 동일한 종류의 나무를 생성할 경우 메모리 사용량이 230MB로 절반 이상 줄었다.
이는 oak-red 나무와 acacia-brown 나무를 생성할 때, 처음 생성후 두번째 부터는 캐시에 저장된 데이터를 가지고 오기 때문에 각각 oak-red의 경우 60MB2, acacia-brown의 경우 110MB2 총 340MB의 메모리를 아낀 것인데(계산식은 코드참조, 실제 사용량 아님) 나무를 훨씬 많이 심는 실제 사용환경에서는 비교가 불가능할 정도의 성능차이를 보일 것이다.