Factory Method (Fabriksmetod)
Introduktion
Factory Method är ett skapande mönster som definierar ett gränssnitt för att skapa objekt, men låter underklasser bestämma vilken klass som ska instansieras.
Problem
Du bygger ett logistiksystem. I den första versionen hanterar appen bara lastbilstransporter, och koden är full av new Truck()-anrop.
När kunden sen vill lägga till sjöfrakt måste du gå igenom hela kodbasen och ändra varje anrop. Och när flygtransport tillkommer? Samma sak igen.
Lösning
Factory Method ersätter direkta konstruktoranrop med ett anrop till en fabriksmetod. Underklassen bestämmer vilken konkret klass som skapas. Klientkoden ser bara gränssnittet — inte den konkreta klassen.
När ska Factory Method användas?
- När du inte vet i förväg exakt vilka klasser din kod ska arbeta med.
- När du vill ge användare av ditt bibliotek möjlighet att utöka dess komponenter.
- För att minska beroenden av konkreta klasser.
Struktur
| Roll | Ansvar |
|---|---|
| Creator | Deklarerar fabriksmetoden; kan ha en default-implementation. |
| Concrete Creator | Överskuggar fabriksmetoden och returnerar en specifik produkt. |
| Product Interface | Definierar gränssnittet för alla produkter. |
| Concrete Product | En specifik implementation av produktgränssnittet. |
Exempel — Logistiksystem (TypeScript)
Scenariot: Ett logistiksystem som skapar transportobjekt via en fabriksmetod. Klientkoden arbetar mot Transport-gränssnittet och bryr sig inte om det är en lastbil eller ett fartyg.
// ── Product Interface ─────────────────────────────────────
interface Transport {
deliver(origin: string, destination: string): void;
}
// ── Concrete Products ─────────────────────────────────────
class Truck implements Transport {
deliver(origin: string, destination: string): void {
console.log(`🚛 Lastbil levererar: ${origin} → ${destination} (via landsväg)`);
}
}
class Ship implements Transport {
deliver(origin: string, destination: string): void {
console.log(`🚢 Fartyg levererar: ${origin} → ${destination} (via havsrutt)`);
}
}
class Airplane implements Transport {
deliver(origin: string, destination: string): void {
console.log(`✈️ Flygplan levererar: ${origin} → ${destination} (direktflyg)`);
}
}
// ── Creator (abstrakt) ────────────────────────────────────
abstract class Logistics {
// Fabriksmetoden — underklassen bestämmer vad som skapas
abstract createTransport(): Transport;
// Affärslogiken använder produkten via gränssnittet
planDelivery(origin: string, destination: string): void {
const transport = this.createTransport();
console.log('Planerar leverans...');
transport.deliver(origin, destination);
}
}
// ── Concrete Creators ─────────────────────────────────────
class RoadLogistics extends Logistics {
createTransport(): Transport { return new Truck(); }
}
class SeaLogistics extends Logistics {
createTransport(): Transport { return new Ship(); }
}
class AirLogistics extends Logistics {
createTransport(): Transport { return new Airplane(); }
}
// ── Klientkod ─────────────────────────────────────────────
function getLogistics(mode: string): Logistics {
if (mode === 'road') return new RoadLogistics();
if (mode === 'sea') return new SeaLogistics();
return new AirLogistics();
}
// Klienten väljer Creator — inte konkret produkt
const logistics = getLogistics('sea');
// Klientkoden behöver inte ändras när nya transporttyper läggs till
logistics.planDelivery('Stockholm', 'Hamburg');
Output:
Planerar leverans...
🚢 Fartyg levererar: Stockholm → Hamburg (via havsrutt)
Fördelar
- Single Responsibility Principle — Produktskapandet är på ett ställe.
- Open/Closed Principle — Lägg till nya produkttyper utan att ändra klientkoden.
- Löst kopplad kod — klienten arbetar mot gränssnittet.
Nackdelar
- Koden kan bli mer komplex med många underklasser.
Av Victor Hernandez från Bytebase.se