Zuletzt aktualisiert am 16.12.2025 7 Minuten Lesezeit

Dependency Injection

Dependency Injection (DI) ist ein Entwurfsmuster der objektorientierten Programmierung, bei dem Abhängigkeiten eines Objekts von außen bereitgestellt werden, anstatt dass das Objekt sie selbst erzeugt. Das Konzept basiert auf dem Prinzip der Inversion of Control (IoC) und ist eines der wichtigsten Muster für wartbare, testbare und flexible Software. DI ist heute in praktisch jedem professionellen Softwareprojekt anzutreffen und bildet das Fundament moderner Frameworks wie Spring (Java), .NET Core oder Angular.

Grundprinzip der Dependency Injection

Traditionell erzeugt ein Objekt seine Abhängigkeiten selbst. Wenn beispielsweise ein UserService einen UserRepository benötigt, würde er diesen mit new UserRepository() selbst instanziieren. Das Problem: Der Service ist damit fest an diese konkrete Implementierung gebunden.

Mit Dependency Injection ändert sich dieser Ablauf fundamental: Der UserService erwartet, dass ihm ein UserRepository von außen übergeben wird. Eine externe Komponente – typischerweise ein DI Container – übernimmt die Verantwortung für die Objekterzeugung und das Zusammenfügen aller Abhängigkeiten.

// OHNE Dependency Injection - enge Kopplung
public class UserService {
    private UserRepository repository = new UserRepository();

    public User findUser(int id) {
        return repository.findById(id);
    }
}

// MIT Dependency Injection - lose Kopplung
public class UserService {
    private final UserRepository repository;

    // Die Abhängigkeit wird von außen injiziert
    public UserService(UserRepository repository) {
        this.repository = repository;
    }

    public User findUser(int id) {
        return repository.findById(id);
    }
}

Die drei Arten der Dependency Injection

Es gibt drei etablierte Techniken, wie Abhängigkeiten in ein Objekt injiziert werden können. Jede hat ihre Vor- und Nachteile und eignet sich für unterschiedliche Situationen.

Constructor Injection (empfohlen)

Bei der Constructor Injection werden alle Abhängigkeiten als Parameter an den Konstruktor übergeben. Dies ist die bevorzugte Methode, weil sie garantiert, dass ein Objekt nie in einem unvollständigen Zustand existieren kann.

public class OrderService {
    private readonly IOrderRepository _repository;
    private readonly IEmailService _emailService;

    // Alle Abhängigkeiten sind im Konstruktor sichtbar
    public OrderService(IOrderRepository repository, IEmailService emailService) {
        _repository = repository;
        _emailService = emailService;
    }

    public void PlaceOrder(Order order) {
        _repository.Save(order);
        _emailService.SendConfirmation(order.CustomerEmail);
    }
}

Vorteile: Das Objekt ist immer vollständig initialisiert, Abhängigkeiten sind explizit sichtbar, fördert Immutabilität (final/readonly). Nachteil: Viele Parameter können auf zu viele Verantwortlichkeiten hindeuten.

Setter Injection

Bei der Setter Injection werden Abhängigkeiten über Setter-Methoden nach der Objektinstanziierung bereitgestellt. Diese Methode bietet mehr Flexibilität, da Abhängigkeiten optional sein oder später geändert werden können.

public class ReportGenerator {
    private ILogger logger;

    // Optionale Abhängigkeit über Setter
    public void setLogger(ILogger logger) {
        this.logger = logger;
    }

    public void generateReport() {
        if (logger != null) {
            logger.log("Generating report...");
        }
        // Report-Generierung
    }
}

Nachteil: Es ist schwer sicherzustellen, dass alle erforderlichen Abhängigkeiten injiziert wurden, bevor das Objekt verwendet wird. Das kann zu Runtime-Fehlern führen.

Interface Injection

Bei der Interface Injection definiert ein Interface eine Methode zum Injizieren der Abhängigkeit. Diese Variante wird selten verwendet, da sie zusätzliche Komplexität einführt, ohne signifikante Vorteile gegenüber Constructor oder Setter Injection zu bieten.

Vorteile von Dependency Injection

Die Adoption von Dependency Injection in professionellen Softwareprojekten bringt substanzielle, messbare Vorteile mit sich:

Vorteil Beschreibung
Testbarkeit Abhängigkeiten können in Tests durch Mock-Objekte ersetzt werden
Lose Kopplung Komponenten kennen nur Interfaces, nicht konkrete Implementierungen
Wartbarkeit Änderungen an einer Komponente erfordern keine Änderungen an anderen
Wiederverwendbarkeit Komponenten können in verschiedenen Kontexten verwendet werden
Zentralisierte Konfiguration Alle Abhängigkeiten werden an einem Ort verwaltet

Verbesserte Testbarkeit

Einer der wichtigsten Vorteile ist die dramatisch verbesserte Testbarkeit. Bei der Verwendung von DI können Abhängigkeiten in Unit Tests leicht durch Mock-Objekte oder Stubs ersetzt werden. So kannst du beispielsweise einen Service testen, ohne eine echte Datenbankverbindung zu benötigen:

[Test]
public void PlaceOrder_ShouldSendConfirmationEmail() {
    // Arrange - Mock-Objekte erstellen
    var mockRepository = new Mock<IOrderRepository>();
    var mockEmailService = new Mock<IEmailService>();

    // Service mit Mocks injizieren
    var orderService = new OrderService(
        mockRepository.Object, 
        mockEmailService.Object
    );

    // Act
    orderService.PlaceOrder(new Order { CustomerEmail = "test@example.com" });

    // Assert - Prüfen, ob E-Mail gesendet wurde
    mockEmailService.Verify(
        x => x.SendConfirmation("test@example.com"), 
        Times.Once
    );
}

DI Container und IoC Container

Ein Dependency Injection Container (auch IoC Container genannt) ist eine Softwarekomponente, die die Instanziierung und Zusammensetzung von Objekten automatisiert. Der Container speichert Informationen über alle Typen und deren Abhängigkeiten und orchestriert die Zusammensetzung aller Objekte.

Der Container funktioniert in mehreren Schritten:

  1. Registrierung: Alle Typen und deren Abhängigkeiten werden beim Container registriert
  2. Auflösung: Wenn ein Objekt benötigt wird, analysiert der Container die Abhängigkeiten und erzeugt alle erforderlichen Objekte
  3. Lebenszyklus-Verwaltung: Der Container verwaltet, wie lange Objekte existieren

Service-Lebensdauer

DI Container unterstützen verschiedene Lebensdauer-Strategien für Services:

Lebensdauer Beschreibung Beispiel-Anwendungsfall
Singleton Eine einzige Instanz für die gesamte Anwendung Konfiguration, Logging
Scoped Eine Instanz pro Anfrage/Kontext Datenbankkontext pro HTTP-Request
Transient Jedes Mal eine neue Instanz Zustandsbehaftete Services
// .NET Core DI Container Registrierung
public void ConfigureServices(IServiceCollection services) {
    // Singleton - eine Instanz für die gesamte Anwendung
    services.AddSingleton<IConfiguration, AppConfiguration>();

    // Scoped - eine Instanz pro HTTP-Request
    services.AddScoped<IUserRepository, UserRepository>();

    // Transient - jedes Mal eine neue Instanz
    services.AddTransient<IEmailService, SmtpEmailService>();
}

Dependency Injection und SOLID-Prinzipien

Dependency Injection steht in direkter Beziehung zu den SOLID-Prinzipien, insbesondere zum Dependency Inversion Principle (DIP). Dieses Prinzip besagt:

  • High-Level-Module sollten nicht von Low-Level-Modulen abhängen - beide sollten von Abstraktionen abhängen
  • Abstraktionen sollten nicht von Details abhängen - Details sollten von Abstraktionen abhängen

DI ist die primäre praktische Realisierung dieses Prinzips. Durch die Verwendung von DI arbeiten Klassen mit Abstraktionen (Interfaces) statt mit konkreten Implementierungen. Eine Geschäftslogik-Klasse weiß nicht, wie Daten gespeichert werden - sie arbeitet nur mit einer abstrakten IRepository-Schnittstelle.

Bekannte DI Frameworks

In verschiedenen Programmiersprachen und Ökosystemen gibt es etablierte DI-Frameworks:

Framework Sprache/Plattform Besonderheiten
Spring Framework Java Das dominierende DI-Framework im Java-Ökosystem
.NET Core DI C# In das Framework integriert, kein externes Paket nötig
Angular DI TypeScript Dekoratoren-basiert mit @Injectable
Autofac / Ninject C# Erweiterte Features für .NET
Google Guice Java Leichtgewichtiges Framework von Google
Dagger Java/Kotlin Compile-Time DI für Android

Spring Framework (Java)

@Service
public class ProductService {
    private final ProductRepository repository;

    @Autowired // Spring injiziert automatisch die Abhängigkeit
    public ProductService(ProductRepository repository) {
        this.repository = repository;
    }

    public List<Product> getAllProducts() {
        return repository.findAll();
    }
}

Angular (TypeScript)

import { Injectable, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DataService {
    getData(): string[] {
        return ['Item 1', 'Item 2', 'Item 3'];
    }
}

@Component({
    selector: 'app-list',
    template: `<ul><li *ngFor="let item of items">{{ item }}</li></ul>`
})
export class ListComponent {
    private dataService = inject(DataService);
    items = this.dataService.getData();
}

Häufige Fehler und Anti-Patterns

Bei der Anwendung von Dependency Injection gibt es einige typische Fallstricke, die du vermeiden solltest:

  • Service Locator Anti-Pattern: Statt Abhängigkeiten injizieren zu lassen, werden sie von einem globalen Container abgerufen. Das versteckt die Abhängigkeiten und erschwert das Testen.
  • Control Freak: Eine Klasse erzeugt ihre Abhängigkeiten selbst mit new, anstatt sie injizieren zu lassen.
  • Zirkuläre Abhängigkeiten: Klasse A hängt von B ab und B von A - dies führt zu Deadlocks im Container.
  • Zu viele Abhängigkeiten: Ein Konstruktor mit vielen Parametern deutet oft auf eine Verletzung des Single Responsibility Principle hin.
  • Falsche Service-Lebensdauer: Ein Scoped Service in einem Singleton kann zu Datenlecks führen.

Dependency Injection in der IT-Ausbildung

Für angehende Fachinformatiker für Anwendungsentwicklung ist das Verständnis von Dependency Injection nicht nur wertvoll, sondern notwendig. DI ist relevant in mehreren Lernfeldern:

  • Lernfeld 5 (Software zur Verwaltung von Daten): Testprozesse und Softwaredokumentation nutzen DI für Mock-Objekte
  • Lernfeld 11a (Funktionalität in Anwendungen realisieren): Modulare Softwarekomponenten mit klaren Schnittstellen

In der realen Berufspraxis ist DI alltäglich - praktisch jedes professionelle Softwareprojekt nutzt Dependency Injection. Wer DI versteht, kann sofort in professionellen Codebases arbeiten.

Quellen und weiterführende Links