<EbeneX/>
Web Praxis · Updated 3. März 2026

Dependency Injection

Definition

Ein Entwurfsmuster, bei dem Abhängigkeiten einer Klasse nicht intern erstellt, sondern von außen übergeben werden – für bessere Testbarkeit, Flexibilität und lose Kopplung.

Fortgeschritten 3 Min. Lesezeit EN: Dependency Injection (DI)

Einfach erklärt

Stell dir vor, eine Klasse ReportGenerator braucht eine Datenbankverbindung. Ohne DI erstellt sie diese selbst:

class ReportGenerator:
    def __init__(self):
        self.db = PostgresDatabase("localhost:5432")  # Hardcodiert!

Das Problem: Für Tests braucht man eine echte Datenbank. Und wenn man die Datenbank wechselt, muss man den Code ändern. Die Klasse ist eng an eine konkrete Implementierung gekoppelt.

Mit Dependency Injection wird die Abhängigkeit von außen übergeben:

class ReportGenerator:
    def __init__(self, db: Database):  # Interface, nicht konkrete Klasse
        self.db = db

Jetzt kann man im Test eine Mock-Datenbank übergeben, in Production die echte – der ReportGenerator merkt keinen Unterschied.

Verbindung zu SOLID

DI ist die praktische Umsetzung von zwei SOLID-Prinzipien:

  • D – Dependency Inversion Principle: High-Level-Module sollen nicht von Low-Level-Modulen abhängen, beide sollen von Abstraktionen abhängen
  • O – Open/Closed Principle: Klassen sollen offen für Erweiterung, geschlossen für Modifikation sein – durch DI kann man Verhalten ändern, ohne Code zu ändern

Anti-Pattern: Service Locator

DI hat ein häufig verwechseltes Anti-Pattern:

# ❌ Service Locator: Abhängigkeiten werden aktiv geholt
class ReportGenerator:
    def __init__(self):
        self.db = ServiceLocator.get(Database)  # Versteckte Abhängigkeit!

# ✅ Dependency Injection: Abhängigkeiten werden übergeben
class ReportGenerator:
    def __init__(self, db: Database):  # Explizite Abhängigkeit
        self.db = db

Der Service Locator versteckt Abhängigkeiten – man sieht nicht am Konstruktor, was die Klasse braucht. DI macht Abhängigkeiten explizit und sichtbar.

Technischer Deep Dive

Die drei Injektionsarten

# 1. Constructor Injection (empfohlen – alle Abhängigkeiten sofort sichtbar)
class LLMService:
    def __init__(self, llm_client: LLMClient, cache: Cache):
        self.client = llm_client
        self.cache = cache

# 2. Setter Injection (für optionale Abhängigkeiten)
class LLMService:
    def set_cache(self, cache: Cache) -> None:
        self.cache = cache

# 3. Property Injection (selten, nur wenn Konstruktor nicht kontrollierbar)
class LLMService:
    cache: Cache = InMemoryCache()  # Default-Implementierung

Testbarkeit durch DI

# Production: echte Implementierungen
service = LLMService(
    llm_client=OpenAIClient(api_key=settings.OPENAI_KEY),
    cache=RedisCache(host="redis:6379")
)

# Unit Test: Mock-Implementierungen – kein Netzwerk, kein Redis
service = LLMService(
    llm_client=MockLLMClient(responses={"Hallo": "Welt"}),
    cache=InMemoryCache()
)
result = service.generate("Hallo")
assert result == "Welt"

TypeScript mit InversifyJS

import { injectable, inject, Container } from 'inversify';

// Interfaces definieren
interface LLMClient { generate(prompt: string): Promise<string>; }
interface Cache { get(key: string): string | null; set(key: string, value: string): void; }

// Implementierungen
@injectable()
class OpenAIClient implements LLMClient {
  async generate(prompt: string) { /* ... */ }
}

@injectable()
class RedisCache implements Cache {
  get(key: string) { /* ... */ }
  set(key: string, value: string) { /* ... */ }
}

// Service mit injizierten Abhängigkeiten
@injectable()
class LLMService {
  constructor(
    @inject('LLMClient') private client: LLMClient,
    @inject('Cache') private cache: Cache,
  ) {}
}

// Container konfigurieren
const container = new Container();
container.bind<LLMClient>('LLMClient').to(OpenAIClient);
container.bind<Cache>('Cache').to(RedisCache);
container.bind(LLMService).toSelf();

const service = container.get(LLMService);

FastAPI Dependency Injection

FastAPI hat DI eingebaut – besonders nützlich für Datenbankverbindungen und Auth:

from fastapi import Depends, HTTPException
from functools import lru_cache

# Dependency-Funktionen
def get_llm_client() -> LLMClient:
    return OpenAIClient(api_key=settings.OPENAI_KEY)

def get_db() -> Generator:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
) -> User:
    user = db.query(User).filter(User.token == token).first()
    if not user:
        raise HTTPException(status_code=401)
    return user

# Endpunkt mit mehreren Abhängigkeiten
@app.post("/generate")
async def generate(
    prompt: str,
    llm: LLMClient = Depends(get_llm_client),
    user: User = Depends(get_current_user),
):
    return await llm.generate(f"User {user.name}: {prompt}")

Scopes in DI-Containern

DI-Container unterstützen verschiedene Lebenszyklen:

ScopeBedeutungTypischer Use Case
TransientNeue Instanz bei jeder AnfrageStateless Services
ScopedEine Instanz pro RequestDatenbankverbindung
SingletonEine Instanz für die gesamte AppKonfiguration, HTTP-Client

Dependency Injection ist wie ein Restaurant, das seine Zutaten geliefert bekommt, statt selbst anzubauen: Der Koch (Klasse) kocht das Gericht, aber er pflanzt das Gemüse nicht selbst an. Jemand anderes (der Injector) liefert die Zutaten. So kann man dem Koch für Tests auch Kunstgemüse geben – er merkt keinen Unterschied.

Klassen erhalten ihre Abhängigkeiten von außen statt sie selbst zu erstellen

Ermöglicht einfaches Austauschen von Abhängigkeiten – besonders für Unit Tests

Fördert das Dependency-Inversion-Prinzip aus SOLID

Unit Testing

Echte Datenbankverbindungen durch Mock-Objekte ersetzen, ohne den Code zu ändern

Konfigurierbare KI-Pipelines

LLM-Provider austauschen (OpenAI ↔ Claude) ohne Änderungen an der Business-Logik

Plugin-Architekturen

Verschiedene Implementierungen eines Interfaces zur Laufzeit einsetzen

Was ist der Unterschied zwischen Dependency Injection und Dependency Inversion?

Dependency Inversion ist ein Prinzip (aus SOLID): High-Level-Module sollen nicht von Low-Level-Modulen abhängen, beide sollen von Abstraktionen abhängen. Dependency Injection ist eine Technik, um dieses Prinzip umzusetzen: Abhängigkeiten werden über Interfaces definiert und von außen injiziert.

Welche Arten von Dependency Injection gibt es?

Constructor Injection: Abhängigkeiten werden im Konstruktor übergeben (empfohlen). Setter Injection: Abhängigkeiten werden über Setter-Methoden gesetzt. Property Injection: Direkte Zuweisung an Properties. Constructor Injection ist am klarsten, weil alle Abhängigkeiten sofort sichtbar sind.

Brauche ich einen DI-Container?

Nicht unbedingt. In kleinen Projekten reicht manuelles DI: Objekte im Hauptprogramm erstellen und übergeben. DI-Container (wie Spring oder InversifyJS) automatisieren das für große Projekte mit vielen Abhängigkeiten.

Dein persönliches Share-Bild für Instagram – 1080×1080px, bereit zum Posten.