자바로 작성된 플라이웨이트
플라이웨이트는 구조 패턴이며 프로그램들이 객체들의 메모리 소비를 낮게 유지하여 방대한 양의 객체들을 지원할 수 있도록 합니다.
이 패턴은 여러 객체 사이의 객체 상태를 공유하여 위를 달성합니다. 다르게 설명하자면 플라이웨이트는 다른 객체들이 공통으로 사용하는 데이터를 캐싱하여 RAM을 절약합니다.
복잡도:
인기도:
사용 사례들: 플라이웨이트 패턴의 유일한 목적은 메모리 섭취를 최소화하는 것입니다. 당신의 프로그램이 RAM 부족으로 문제를 겪지 않는다면 당분간 이 패턴을 무시할 수 있습니다.
핵심 자바 라이브러리의 플라이웨이트 예시들:
식별: 플라이웨이트는 새로운 객체들 대신 캐싱 된 객체들을 반환하는 생성 메서드의 유무로 식별될 수 있습니다.
숲을 렌더링하기
이 예시에서 우리는 숲(백만 그루의 나무)을 렌더링할 것입니다! 각 트리는 자체 객체로 표시되며 이 객체는 일부 상태(좌표, 질감 등)를 가집니다. 현재 프로그램은 기본 작업은 수행하나, 많은 양의 RAM을 소모합니다.
그 이유는 너무 많은 트리 객체들이 중복된 데이터(예: 이름, 질감, 색상)를 포함하기 때문입니다. 그러므로 플라이웨이트 패턴을 적용하고 위 값들을 별도의 플라이웨이트 객체(TreeType 클래스)에 저장할 수 있습니다. 이제 우리는 수천 개의 Tree 객체들에 같은 데이터를 저장하는 대신 특정 값들의 집합을 지닌 플라이웨이트 객체 중 하나를 참조합니다.
클라이언트 코드는 아무것도 눈치채지 못할 것입니다. 왜냐하면 플라이웨이트 객체들 재사용의 복잡성은 플라이웨이트 팩토리 내부에 묻혀 있기 때문입니다.
trees
trees/Tree.java: 각 트리에 대해 고유한 상태를 포함합니다
package refactoring_guru.flyweight.example.trees; import java.awt.*; public 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(Graphics g) { type.draw(g, x, y); } } trees/TreeType.java: 여러 트리가 공유하는 상태를 포함합니다
package refactoring_guru.flyweight.example.trees; import java.awt.*; public class TreeType { private String name; private Color color; private String otherTreeData; public TreeType(String name, Color color, String otherTreeData) { this.name = name; this.color = color; this.otherTreeData = otherTreeData; } public void draw(Graphics g, int x, int y) { g.setColor(Color.BLACK); g.fillRect(x - 1, y, 3, 5); g.setColor(color); g.fillOval(x - 5, y - 10, 10, 10); } } trees/TreeFactory.java: 플라이웨이트 생성의 복잡성을 캡슐화합니다
package refactoring_guru.flyweight.example.trees; import java.awt.*; import java.util.HashMap; import java.util.Map; public class TreeFactory { static Map<String, TreeType> treeTypes = new HashMap<>(); public static TreeType getTreeType(String name, Color color, String otherTreeData) { TreeType result = treeTypes.get(name); if (result == null) { result = new TreeType(name, color, otherTreeData); treeTypes.put(name, result); } return result; } } forest
forest/Forest.java: 우리가 그려야 할 숲
package refactoring_guru.flyweight.example.forest; import refactoring_guru.flyweight.example.trees.Tree; import refactoring_guru.flyweight.example.trees.TreeFactory; import refactoring_guru.flyweight.example.trees.TreeType; import javax.swing.*; import java.awt.*; import java.util.ArrayList; import java.util.List; public class Forest extends JFrame { private List<Tree> trees = new ArrayList<>(); public void plantTree(int x, int y, String name, Color color, String otherTreeData) { TreeType type = TreeFactory.getTreeType(name, color, otherTreeData); Tree tree = new Tree(x, y, type); trees.add(tree); } @Override public void paint(Graphics graphics) { for (Tree tree : trees) { tree.draw(graphics); } } } Demo.java: 클라이언트 코드
package refactoring_guru.flyweight.example; import refactoring_guru.flyweight.example.forest.Forest; import java.awt.*; public class Demo { static int CANVAS_SIZE = 500; static int TREES_TO_DRAW = 1000000; static int TREE_TYPES = 2; public static void main(String[] args) { Forest forest = new Forest(); for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) { forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE), "Summer Oak", Color.GREEN, "Oak texture stub"); forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE), "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub"); } forest.setSize(CANVAS_SIZE, CANVAS_SIZE); forest.setVisible(true); System.out.println(TREES_TO_DRAW + " trees drawn"); System.out.println("---------------------"); System.out.println("Memory usage:"); System.out.println("Tree size (8 bytes) * " + TREES_TO_DRAW); System.out.println("+ TreeTypes size (~30 bytes) * " + TREE_TYPES + ""); System.out.println("---------------------"); System.out.println("Total: " + ((TREES_TO_DRAW * 8 + TREE_TYPES * 30) / 1024 / 1024) + "MB (instead of " + ((TREES_TO_DRAW * 38) / 1024 / 1024) + "MB)"); } private static int random(int min, int max) { return min + (int) (Math.random() * ((max - min) + 1)); } } OutputDemo.png: 스크린샷
OutputDemo.txt: RAM 사용 통계들
1000000 trees drawn --------------------- Memory usage: Tree size (8 bytes) * 1000000 + TreeTypes size (~30 bytes) * 2 --------------------- Total: 7MB (instead of 36MB)