Prototype (Prototyp)
Introduktion
Prototype är ett skapande mönster som låter dig kopiera befintliga objekt utan att göra din kod beroende av deras klasser.
Problem
Du har ett komplext objekt som tagit lång tid att konfigurera — det har dragit data från en databas, gjort API-anrop och beräknat dussintals fält. Nu behöver du ett nytt objekt som nästan är identiskt, med bara några få ändringar.
Att skapa ett nytt objekt från grunden är dyrt. Att komma åt objektets privata fält utifrån för att kopiera dem är omöjligt. Du behöver klona det.
Lösning
Prototype delegerar kloningsansvaret till objektet självt. Gränssnittet deklarerar en clone()-metod. Varje klass implementerar sin egen kloning — den har tillgång till sina egna privata fält. I Java implementerar man Cloneable eller skapar en kopierings-konstruktor.
När ska Prototype användas?
- När du behöver kopior av objekt vars klasser du inte känner till i förväg.
- När dina objekt är dyra att skapa och en kopia är billigare.
- När du vill reducera antalet subklasser som bara skiljer sig i initialiseringsvärden.
Struktur
| Roll | Ansvar |
|---|---|
| Prototype Interface | Deklarerar clone()-metoden. |
| Concrete Prototype | Implementerar kloningslogiken inklusive kopiering av privata fält. |
| Prototype Registry | Valfritt — lagrar förbyggda prototyper som kan klonas på begäran. |
Exempel — Spelkaraktärer (Java)
Scenariot: Förbyggda karaktärsmalar som klonas och anpassas — dyrare än att skapa från grunden.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// ── Prototype Interface ───────────────────────────────────
public interface Cloneable<T> {
T clone();
}
// ── Concrete Prototype ────────────────────────────────────
public class GameCharacter implements Cloneable<GameCharacter> {
private String name;
private String characterClass;
private int level;
private int health;
private int mana;
private List<String> skills;
private Map<String, Integer> attributes;
// Standardkonstruktor
public GameCharacter(String name, String characterClass) {
this.name = name;
this.characterClass = characterClass;
this.skills = new ArrayList<>();
this.attributes = new HashMap<>();
}
// Kopieringskonstruktor — djupkopia
private GameCharacter(GameCharacter source) {
this.name = source.name;
this.characterClass = source.characterClass;
this.level = source.level;
this.health = source.health;
this.mana = source.mana;
this.skills = new ArrayList<>(source.skills); // djupkopia
this.attributes = new HashMap<>(source.attributes); // djupkopia
}
@Override
public GameCharacter clone() {
return new GameCharacter(this);
}
// Fluent setters för enkel konfiguration
public GameCharacter withName(String name) { this.name = name; return this; }
public GameCharacter withLevel(int level) { this.level = level; return this; }
public GameCharacter withHealth(int health) { this.health = health; return this; }
public GameCharacter withMana(int mana) { this.mana = mana; return this; }
public GameCharacter addSkill(String skill) {
this.skills.add(skill);
return this;
}
public GameCharacter setAttribute(String attr, int value) {
this.attributes.put(attr, value);
return this;
}
@Override
public String toString() {
return String.format(
"%s [%s] Lvl %d | HP:%d MP:%d | Skills:%s | Attribut:%s",
name, characterClass, level, health, mana, skills, attributes
);
}
}
// ── Prototype Registry ────────────────────────────────────
public class CharacterRegistry {
private final Map<String, GameCharacter> templates = new HashMap<>();
public void register(String key, GameCharacter character) {
templates.put(key, character);
}
public GameCharacter get(String key) {
GameCharacter template = templates.get(key);
if (template == null)
throw new IllegalArgumentException("Ingen mall hittades: " + key);
return template.clone(); // Returnerar alltid en kopia
}
}
// ── Klientkod ─────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
// Bygg dyra grundmallar en gång
GameCharacter wizardTemplate = new GameCharacter("Mall", "Magiker")
.withLevel(1)
.withHealth(80)
.withMana(150)
.addSkill("Eldkula")
.addSkill("Teleport")
.setAttribute("intelligens", 18)
.setAttribute("styrka", 6);
GameCharacter warriorTemplate = new GameCharacter("Mall", "Krigare")
.withLevel(1)
.withHealth(200)
.withMana(20)
.addSkill("Svärdslag")
.addSkill("Sköldblock")
.setAttribute("styrka", 18)
.setAttribute("intelligens", 6);
// Registrera mallarna
CharacterRegistry registry = new CharacterRegistry();
registry.register("magiker", wizardTemplate);
registry.register("krigare", warriorTemplate);
// Klona och anpassa — snabbt och billigt
GameCharacter player1 = registry.get("magiker").withName("Gandalf").withLevel(50);
GameCharacter player2 = registry.get("magiker").withName("Saruman").withLevel(48).addSkill("Kontrollera sinnen");
GameCharacter player3 = registry.get("krigare").withName("Aragorn").withLevel(35);
System.out.println("Skapade karaktärer:");
System.out.println(player1);
System.out.println(player2);
System.out.println(player3);
// Verifiera att det är djupkopior — skills är oberoende
System.out.println("\nGandalf skills: " + player1);
System.out.println("Saruman skills: " + player2);
System.out.println("(Sarumanss extra skill påverkar inte Gandalf)");
}
}
Output:
Skapade karaktärer:
Gandalf [Magiker] Lvl 50 | HP:80 MP:150 | Skills:[Eldkula, Teleport] | Attribut:{intelligens=18, styrka=6}
Saruman [Magiker] Lvl 48 | HP:80 MP:150 | Skills:[Eldkula, Teleport, Kontrollera sinnen] | Attribut:{intelligens=18, styrka=6}
Aragorn [Krigare] Lvl 35 | HP:200 MP:20 | Skills:[Svärdslag, Sköldblock] | Attribut:{styrka=18, intelligens=6}
Gandalf skills: Gandalf [Magiker] Lvl 50 | HP:80 MP:150 | Skills:[Eldkula, Teleport] | Attribut:{intelligens=18, styrka=6}
Saruman skills: Saruman [Magiker] Lvl 48 | HP:80 MP:150 | Skills:[Eldkula, Teleport, Kontrollera sinnen] | Attribut:{intelligens=18, styrka=6}
(Sarumanss extra skill påverkar inte Gandalf)
Tips: Använd alltid djupkopia för samlingar och kapslade objekt — annars delar kloner samma referens och mutationer sprids oavsiktligt.
Fördelar
- Klona komplexa objekt utan beroenden till deras konkreta klasser.
- Eliminera upprepad initialisering — klona förbyggda mallar istället.
- Producera komplexa objekt som alternativ till arv.
Nackdelar
- Djupkopiering av komplexa objekt med cirkulära referenser kan vara knepigt.
- Varje klass måste implementera sin egen kloning, vilket kan vara besvärligt för klasser med många privata fält.
Av Victor Hernandez från Bytebase.se