
Poids mouche en Java
Le poids mouche est un patron de conception structurel qui permet à des programmes de limiter leur consommation de mémoire malgré un très grand nombre d’objets.
Ce patron est obtenu en partageant des parties de l’état d’un objet à plusieurs autres objets. En d’autres termes, le poids mouche économise de la RAM en mettant en cache les données identiques chez différents objets.
Complexité :
Popularité :
Exemples d’utilisation : Le poids mouche n’a qu’une seule utilité : minimiser la consommation de mémoire. Si votre programme ne rencontre aucun problème de RAM, ignorez ce patron pour le moment.
Voici quelques exemples tirés des bibliothèques principales de Java :
-
java.lang.Integer#valueOf(int)
(mais aussiBoolean
,Byte
,Character
,Short
,Long
etBigDecimal
)
Identification : Le poids mouche peut être reconnu par une méthode de création qui renvoie des objets du cache plutôt que d’en créer de nouveaux.
Afficher une forêt
Dans cet exemple, nous allons procéder à l’affichage d’une forêt (1 000 000 d’arbres) ! Chaque arbre sera représenté par son propre objet avec un certain état (coordonnées, texture, etc.). Même si le programme remplit bien son rôle, il consomme naturellement énormément de RAM.
La raison est simple : de trop nombreux objets Arbre contiennent des données dupliquées (nom, texture, couleur). Nous pouvons lui appliquer le poids mouche afin de stocker ces valeurs dans des objets poids mouche séparés (la classe TypeArbre
). Plutôt que de stocker les mêmes données des milliers de fois dans des objets Arbre
, nous allons référencer un objet poids mouche avec certaines valeurs.
Le code client ne s’en rendra même pas compte puisque la complexité de la réutilisation des objets poids mouche est cachée à l’intérieur d’une fabrique de poids mouches.
trees
trees/Tree.java: Contient un état unique pour chaque arbre
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: Contient un état partagé entre plusieurs arbres
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: Encapsule la complexité de la création de poids mouches
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: La forêt que nous dessinons
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: Code client
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: Impression d’écran

OutputDemo.txt: Statistiques d’utilisation de la RAM
1000000 trees drawn
---------------------
Memory usage:
Tree size (8 bytes) * 1000000
+ TreeTypes size (~30 bytes) * 2
---------------------
Total: 7MB (instead of 36MB)