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

RollAnsvar
CreatorDeklarerar fabriksmetoden; kan ha en default-implementation.
Concrete CreatorÖverskuggar fabriksmetoden och returnerar en specifik produkt.
Product InterfaceDefinierar gränssnittet för alla produkter.
Concrete ProductEn 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