Singleton & Factory
Zwei Creational Patterns im Detail: Singleton fuer genau eine Instanz, Factory fuer flexible Objekt-Erzeugung - mit Varianten und modernen Alternativen.
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.