Zum Inhalt springen
Design Patterns Fortgeschritten 25 min

Singleton & Factory

Zwei Creational Patterns im Detail: Singleton fuer genau eine Instanz, Factory fuer flexible Objekt-Erzeugung - mit Varianten und modernen Alternativen.

Aktualisiert:
Inhaltsverzeichnis

Singleton & Factory

Zwei der beruehmtesten GoF-Patterns aus der Creational-Kategorie. Beide in jeder Codebasis anzutreffen - beide mit modernen Alternativen.

Singleton

Problem: Ich will, dass es genau eine Instanz meiner Klasse gibt - z.B. fuer Logger, Config, Datenbank-Connection.

Loesung: Die Klasse selbst kontrolliert, dass nur eine Instanz existiert.

Klassisches Java-Beispiel

public class Konfiguration {
    private static Konfiguration instance;
    private String apiKey;

    private Konfiguration() {
        // privat - von aussen nicht instanziierbar
    }

    public static Konfiguration getInstance() {
        if (instance == null) {
            instance = new Konfiguration();
        }
        return instance;
    }
}

Verwendung:

Konfiguration k = Konfiguration.getInstance();

Kein new - immer die gleiche Instanz.

TypeScript-Beispiel

class Logger {
  private static instance: Logger;

  private constructor() {}

  static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger();
    }
    return Logger.instance;
  }

  log(msg: string) {
    console.log(`[LOG] ${msg}`);
  }
}

Logger.getInstance().log("Hallo");

Python - Module selbst sind Singletons

In Python brauchst du das selten explizit:

# config.py
api_key = None
debug = False

def load():
    global api_key, debug
    # ...
# andere datei
import config

config.load()
print(config.api_key)

Python-Module sind automatisch Singletons - sie werden einmal geladen und dann gecached.

Wann ist Singleton sinnvoll?

  • Logger - eine zentrale Log-Instanz
  • Config - eine Quelle der Wahrheit
  • DB-Connection-Pool - nicht mehrere Pools parallel
  • Cache - ein gemeinsamer Cache

Wann NICHT?

Singleton hat viel Kritik bekommen:

  • Globaler Zustand = schwer zu testen
  • Versteckte Abhaengigkeiten - wer nutzt den Singleton wirklich?
  • Tight Coupling - Klassen sind gebunden
  • Concurrency-Probleme - mehrere Threads?

Moderne Alternative: Dependency Injection

class Logger {
  log(msg: string) { /* ... */ }
}

class UserService {
  constructor(private logger: Logger) {}

  createUser(data: UserData) {
    this.logger.log("Creating user");
    // ...
  }
}

// In der App
const logger = new Logger();
const userService = new UserService(logger);

Der Logger ist nicht ein Singleton - aber du uebergibst ueberall die gleiche Instanz. Vorteile:

  • Einfach testbar (Mock-Logger reinreichen)
  • Abhaengigkeiten sichtbar
  • Kein globaler Zustand

DI-Frameworks (Spring, NestJS, Angular) automatisieren das.

Factory Pattern

Problem: Du willst Objekte erstellen, aber die genaue Klasse haengt von Laufzeit-Parametern ab.

Loesung: Eine Factory-Funktion oder -Klasse kuemmert sich um die richtige Erzeugung.

Einfache Factory-Funktion

class KreditKarte:
    def bezahlen(self, betrag): print(f"Kreditkarte: {betrag}")

class PayPal:
    def bezahlen(self, betrag): print(f"PayPal: {betrag}")

class Sepa:
    def bezahlen(self, betrag): print(f"SEPA: {betrag}")

def zahlung_erstellen(typ: str):
    if typ == "kreditkarte":
        return KreditKarte()
    if typ == "paypal":
        return PayPal()
    if typ == "sepa":
        return Sepa()
    raise ValueError(f"Unbekannter Typ: {typ}")

# Nutzung
z = zahlung_erstellen("paypal")
z.bezahlen(42.50)

Factory Method

Vererbungs-basierte Variante:

from abc import ABC, abstractmethod

class Zahlung(ABC):
    @abstractmethod
    def bezahlen(self, betrag): ...

class KreditKarte(Zahlung):
    def bezahlen(self, betrag): ...

class PayPal(Zahlung):
    def bezahlen(self, betrag): ...

class ZahlungFactory(ABC):
    @abstractmethod
    def erstellen(self) -> Zahlung: ...

    def process(self, betrag):
        z = self.erstellen()
        z.bezahlen(betrag)

class KreditKarteFactory(ZahlungFactory):
    def erstellen(self): return KreditKarte()

class PayPalFactory(ZahlungFactory):
    def erstellen(self): return PayPal()

Abstract Factory

Eine Factory fuer Familien von Objekten:

class ThemeFactory(ABC):
    @abstractmethod
    def button(self): ...
    @abstractmethod
    def input(self): ...

class DarkTheme(ThemeFactory):
    def button(self): return DarkButton()
    def input(self): return DarkInput()

class LightTheme(ThemeFactory):
    def button(self): return LightButton()
    def input(self): return LightInput()

Wenn du DarkTheme waehlst, bekommst du nur dark-Varianten - nie ein Dark-Button mit Light-Input.

In Python: Einfacher mit Dict

Statt Factory-Klassen oft einfacher:

registry = {
    "kreditkarte": KreditKarte,
    "paypal": PayPal,
    "sepa": Sepa,
}

def zahlung_erstellen(typ: str):
    cls = registry.get(typ)
    if not cls:
        raise ValueError(typ)
    return cls()

Dict statt if-else-Kaskade. Sehr Pythonic.

Wann ist Factory sinnvoll?

  • Unterschiedliche Klassen je nach Config oder User-Input
  • Komplexe Erstellung (Objekt braucht mehrere Schritte zur Initialisierung)
  • Testability - in Tests andere Klassen zurueckliefern
  • Plugins - neue Typen registrieren koennen

Builder Pattern - fuer komplexe Objekte

Wenn ein Konstruktor viele Parameter hat, wird der Aufruf unuebersichtlich:

# Schlecht
nutzer = Nutzer("Anna", 28, None, "Berlin", True, ["admin"], None, ...)

Builder strukturiert das:

class NutzerBuilder:
    def __init__(self):
        self._daten = {}

    def name(self, n): self._daten["name"] = n; return self
    def alter(self, a): self._daten["alter"] = a; return self
    def stadt(self, s): self._daten["stadt"] = s; return self
    def admin(self): self._daten["ist_admin"] = True; return self

    def build(self):
        return Nutzer(**self._daten)

nutzer = (
    NutzerBuilder()
    .name("Anna")
    .alter(28)
    .stadt("Berlin")
    .admin()
    .build()
)

Moderne Alternativen in Python: Dataclasses mit Default-Werten, Keyword-Arguments:

from dataclasses import dataclass

@dataclass
class Nutzer:
    name: str
    alter: int
    stadt: str = "Unbekannt"
    ist_admin: bool = False

nutzer = Nutzer(name="Anna", alter=28, stadt="Berlin", ist_admin=True)

Kompakter und kaum Nachteile gegenueber Builder.

Prototype Pattern

Problem: Ein Objekt zu klonen ist billiger als von Scratch aufbauen.

import copy

class Konfiguration:
    def __init__(self, port, hosts, db_config):
        self.port = port
        self.hosts = hosts
        self.db_config = db_config

basis = Konfiguration(3000, ["a.com", "b.com"], {"host": "db"})
test = copy.deepcopy(basis)
test.port = 3001

copy.deepcopy macht eine tiefe Kopie. Nuetzlich fuer Config-Varianten.

Object Pool

Problem: Objekte sind teuer zu erstellen - DB-Connections, Threads, Grafik-Ressourcen.

Loesung: Ein Pool haelt Objekte parat und gibt sie bei Bedarf aus.

class ConnectionPool:
    def __init__(self, size):
        self._pool = [Connection() for _ in range(size)]

    def acquire(self):
        return self._pool.pop()

    def release(self, conn):
        self._pool.append(conn)

Praxis: DB-Libraries wie SQLAlchemy, Postgres Drivers bringen sowas mit.

Singleton in JavaScript / TypeScript

Modern nutzt man oft Module Pattern:

// logger.ts
let instance: Logger | null = null;

export function getLogger() {
  if (!instance) instance = new Logger();
  return instance;
}

Oder einfach:

// config.ts
export const config = {
  apiKey: process.env.API_KEY,
  port: Number(process.env.PORT ?? 3000),
};

Der Export wird einmal ausgewertet und ist beim Import automatisch geteilt.

Zusammenfassung

  • Singleton: genau eine Instanz - nuetzlich, aber oft besser mit DI geloest
  • Factory Method: Objekt-Erzeugung via Subklasse
  • Abstract Factory: Familien verwandter Objekte
  • Builder: schrittweiser Aufbau komplexer Objekte (in Python oft Dataclasses genug)
  • Prototype: Klonen statt Neu-Erstellen
  • Object Pool: fuer teure Ressourcen

Im naechsten Kapitel: Adapter und Decorator - zwei Structural Patterns, die Code flexibel erweitern.

Zurรผck zum Design Patterns Kurs