Легковаговик на Java
Легковаговик — це структурний патерн, який економить пам’ять завдяки розподілу спільного стану, винесеного в один об’єкт, між безліччю об’єктів.
Легковаговик дозволяє економити пам’ять, записуючи в кеш однакові дані, що використовуються різними об’єктами.
Складність:
Популярність:
Застосування: Сенс використання Легковаговика — це економія пам’яті. Тому, якщо в програмі немає такої проблеми, ви навряд чи знайдете там приклади Легковаговика.
Приклади Легковаговика в стандартних бібліотеках Java:
java.lang.Integer#valueOf(int)(а такожBoolean,Byte,Character,Short,LongBigDecimal)
Ознаки застосування патерна: Легковаговик можна визначити за створюваними методами класу, які повертають закешовані об’єкти, замість створення нових.
Відтворення лісу
У цьому прикладі ми створимо і намалюємо ліс (1.000.000 дерев)! Кожному дереву відповідає свій особистий об’єкт, що має деякий стан (координати, текстуру та інше). Така програма хоч і працює, але «їсть» занадто багато пам’яті.
Багато дерев мають однакові властивості (назву, текстуру, колір). Тому ми можемо застосувати патерн Легковаговик та закешувати ці властивості в окремих об’єктах TreeType. Тепер замість зберігання цих даних у мільйонах об’єктів дерев Tree, ми будемо посилатися на один з декількох об’єктів-легковаговиків.
Клієнту навіть необов’язково знати про все це. Фабрика легковаговиків TreeType сама подбає про створення нового типу дерева, якщо буде запит щодо дерева з якимись унікальними параметрами.
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: GUI-ліс, який малює дерева
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: Статистика споживання пам’яті
1000000 trees drawn --------------------- Memory usage: Tree size (8 bytes) * 1000000 + TreeTypes size (~30 bytes) * 2 --------------------- Total: 7MB (instead of 36MB)