Flyweight (Flugvikt)
Introduktion
Flyweight är ett strukturmönster som låter dig rymma fler objekt i tillgängligt RAM-minne genom att dela gemensamt tillstånd mellan flera objekt istället för att lagra allt data i varje objekt.
Problem
Du bygger ett skogssimuleringsspel med hundratusentals träd. Varje Tree-objekt lagrar sin position, ålder — men också mesh, texturer och färgdata som väger megabytes. Med 100 000 träd förbrukas enorma mängder minne trots att de flesta träd av samma art delar exakt samma grafik.
Lösning
Flyweight delar upp objektets tillstånd i intrinsic (delat, oföränderligt) och extrinsic (unikt per instans). Det delade tillståndet extraheras till ett Flyweight-objekt som delas av alla instanser av samma typ. Varje objekt lagrar bara sitt unika tillstånd.
När ska Flyweight användas?
- När ett program behöver skapa ett enormt antal liknande objekt.
- När objekten förbrukar för mycket RAM.
- När en stor del av objektets tillstånd kan göras externt och delas.
Struktur
| Roll | Ansvar |
|---|---|
| Flyweight | Lagrar det intrinsiska (delade) tillståndet. |
| Flyweight Factory | Skapar och cachar Flyweight-objekt; returnerar befintliga om möjligt. |
| Context | Lagrar det extrinsiska (unika) tillståndet + en referens till ett Flyweight. |
Exempel — Skogssimulerare (Java)
Scenariot: Hundratusentals träd i ett spel, men bara ett fåtal unika TreeType-objekt med tung grafik.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// ── Flyweight — delar tungt intrinsiskt tillstånd ─────────
public final class TreeType {
private final String name;
private final String color;
private final String texture; // Simulerar tung grafik
public TreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
System.out.println(" [Flyweight skapad] " + name + " (" + texture + ")");
}
public void draw(int x, int y, int age) {
System.out.printf(" 🌲 %s på (%d,%d) ålder=%d färg=%s%n", name, x, y, age, color);
}
public String getName() { return name; }
}
// ── Flyweight Factory ─────────────────────────────────────
public class TreeTypeFactory {
private static final Map<String, TreeType> cache = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
String key = name + "_" + color;
if (!cache.containsKey(key)) {
cache.put(key, new TreeType(name, color, texture));
}
return cache.get(key);
}
public static int getCachedCount() { return cache.size(); }
}
// ── Context — lagrar unikt extrinsiskt tillstånd ──────────
public class Tree {
private final int x;
private final int y;
private final int age;
private final TreeType type; // Referens till delat Flyweight
public Tree(int x, int y, int age, TreeType type) {
this.x = x;
this.y = y;
this.age = age;
this.type = type;
}
public void draw() {
type.draw(x, y, age);
}
}
// ── Forest — hanterar alla träd ───────────────────────────
public class Forest {
private final List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, int age, String name, String color, String texture) {
TreeType type = TreeTypeFactory.getTreeType(name, color, texture);
trees.add(new Tree(x, y, age, type));
}
public void draw() {
System.out.println("Ritar " + trees.size() + " träd:");
for (Tree tree : trees) {
tree.draw();
}
}
public int getTreeCount() { return trees.size(); }
}
// ── Klientkod ─────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
Forest forest = new Forest();
System.out.println("Skapar Flyweight-objekt (en gång per trädtyp):");
// Plantera 10 träd av 3 typer — bara 3 TreeType-objekt skapas
forest.plantTree(10, 20, 5, "Gran", "mörkgrön", "gran_texture_4mb.png");
forest.plantTree(30, 40, 12, "Björk", "vit", "bjork_texture_3mb.png");
forest.plantTree(50, 10, 3, "Gran", "mörkgrön", "gran_texture_4mb.png"); // Återanvänder
forest.plantTree(70, 80, 8, "Ek", "grön", "ek_texture_5mb.png");
forest.plantTree(15, 55, 20, "Björk", "vit", "bjork_texture_3mb.png"); // Återanvänder
forest.plantTree(90, 30, 1, "Gran", "mörkgrön", "gran_texture_4mb.png"); // Återanvänder
forest.plantTree(25, 65, 7, "Ek", "grön", "ek_texture_5mb.png"); // Återanvänder
forest.plantTree(45, 15, 15, "Gran", "mörkgrön", "gran_texture_4mb.png"); // Återanvänder
forest.plantTree(60, 90, 4, "Björk", "vit", "bjork_texture_3mb.png"); // Återanvänder
forest.plantTree(80, 50, 9, "Ek", "grön", "ek_texture_5mb.png"); // Återanvänder
System.out.println();
forest.draw();
System.out.println();
System.out.println("Statistik:");
System.out.println(" Träd i skogen: " + forest.getTreeCount());
System.out.println(" Unika TreeType-objekt: " + TreeTypeFactory.getCachedCount());
System.out.println(" Minnesbesparning: " + forest.getTreeCount() + " träd delar " +
TreeTypeFactory.getCachedCount() + " tunga textureobjekt");
}
}
Output:
Skapar Flyweight-objekt (en gång per trädtyp):
[Flyweight skapad] Gran (gran_texture_4mb.png)
[Flyweight skapad] Björk (bjork_texture_3mb.png)
[Flyweight skapad] Ek (ek_texture_5mb.png)
Ritar 10 träd:
🌲 Gran på (10,20) ålder=5 färg=mörkgrön
🌲 Björk på (30,40) ålder=12 färg=vit
🌲 Gran på (50,10) ålder=3 färg=mörkgrön
🌲 Ek på (70,80) ålder=8 färg=grön
🌲 Björk på (15,55) ålder=20 färg=vit
🌲 Gran på (90,30) ålder=1 färg=mörkgrön
🌲 Ek på (25,65) ålder=7 färg=grön
🌲 Gran på (45,15) ålder=15 färg=mörkgrön
🌲 Björk på (60,90) ålder=4 färg=vit
🌲 Ek på (80,50) ålder=9 färg=grön
Statistik:
Träd i skogen: 10
Unika TreeType-objekt: 3
Minnesbesparning: 10 träd delar 3 tunga textureobjekt
Fördelar
- Drastisk minnesbesparing när många objekt delar gemensamt tillstånd.
- Kan möjliggöra scenarier som annars vore omöjliga på grund av minnesbegränsningar.
Nackdelar
- Ökar kodkomplexiteten — uppdelningen i intrinsiskt/extrinsiskt tillstånd kan vara svår att förstå.
- CPU-prestanda kan påverkas om extrinsiskt tillstånd behöver beräknas om ofta.
- Flyweight-objekt måste vara oföränderliga (immutable) — delat föränderligt tillstånd orsakar buggar.
Av Victor Hernandez från Bytebase.se