In diesem Artikel erklären wir, wie du Dependency Injection in Python mit der Injector-Bibliothek umsetzt. Ziel ist es, die Vorteile
von Dependency Injection zu erläutern und dir zu zeigen, wie du deinen Code durch den Einsatz von injector
effizienter und
wartungsfreundlicher gestalten kannst.
Was ist Dependency Injection in Python?
Dependency Injection (DI) ist ein Entwurfsmuster, das die Kopplung zwischen Klassen reduziert (Loose Coupling). Dies bedeutet, dass Klassen nicht mehr direkt miteinander arbeiten, sondern über Schnittstellen oder gemeinsame Abstraktionen kommunizieren. DI ermöglicht eine bessere Erweiterbarkeit und Testbarkeit des Codes.
Warum Dependency Injection?
Ohne Dependency Injection sind Klassen oft direkt an konkrete Implementierungen gebunden, was Änderungen erschwert und die
Fehleranfälligkeit erhöht. DI löst dieses Problem, indem Abhängigkeiten bereitgestellt und verwaltet werden, anstatt sie direkt in der
Klasse zu erstellen. In Python kann die injector
-Bibliothek für dieses Vorgehen genutzt werden.
Dependency Injection in Python mit einem Wettervorhersage-System
Als Beispiel entwickeln wir ein kleines Wettervorhersage-System mit zwei verschiedenen Vorhersagern: "WeatherSatellite" und " WeatherStation". Beide Vorhersager implementieren die Wettervorhersage, jedoch wollen wir das System so gestalten, dass die konkrete Vorhersagemethode flexibel bleibt.
Beispiel ohne Dependency Injection
class WeatherSatellite:
def get_temperature(self):
return 20
class WeatherStation:
def get_temperature(self):
return 22
class Person:
def __init__(self):
self.weather_satellite = WeatherSatellite()
UML-Diagramm: Aktuelles Design ohne Dependency Injection
Problem: Die Person
-Klasse entscheidet, welche Wettervorhersagemethode verwendet wird. Das führt zu einer starken Kopplung und
erschwert es, die Implementierung zu ändern oder zu erweitern.
Verbesserung: Einführung von Interfaces
Python verwendet keine formalen Interfaces wie andere Sprachen, aber wir können ein gemeinsames Basisinterface oder eine Abstraktion durch
Klassen schaffen. Hier definieren wir eine abstrakte Klasse IWeatherForecasting
:
from abc import ABC, abstractmethod
class IWeatherForecasting(ABC):
@abstractmethod
def get_temperature(self):
pass
class WeatherSatellite(IWeatherForecasting):
def get_temperature(self):
return 20
class WeatherStation(IWeatherForecasting):
def get_temperature(self):
return 22
class Person:
def __init__(self, weather_forecasting: IWeatherForecasting):
self.weather_forecasting = weather_forecasting
UML-Diagramm: Interface Implementierung ohne Dependency Injection
Problem: Obwohl jetzt ein Interface verwendet wird, entscheidet die Person
-Klasse immer noch, welche Implementierung genutzt wird. Das
widerspricht dem DI-Prinzip.
Lösung: Dependency Injection mit injector
Um diese Abhängigkeit vollständig zu lösen, verwenden wir die Bibliothek Injector, um die benötigten Objekte automatisch zu injizieren.
Code-Beispiel: Verwendung von Injector für Dependency Injection
from injector import Injector, inject, Module, singleton, provider
class WeatherModule(Module):
@singleton
@provider
def provide_weather_forecasting(self) -> IWeatherForecasting:
return WeatherSatellite()
class Person:
@inject
def __init__(self, weather_forecasting: IWeatherForecasting):
self.weather_forecasting = weather_forecasting
def main():
injector = Injector([WeatherModule()])
person = injector.get(Person)
print(person.weather_forecasting.get_temperature())
if __name__ == "__main__":
main()
Erklärung
- Module: Definiert, welche Implementierungen für Abhängigkeiten verwendet werden.
- @provider: Gibt an, dass die Methode eine Abhängigkeit bereitstellt.
- @inject: Ermöglicht die Injektion der Abhängigkeiten in den Konstruktor der Klasse.
- injector.get: Erstellt automatisch eine Instanz von
Person
mit den notwendigen Abhängigkeiten.
UML-Diagramm: Design mit Dependency Injection
Vorteil
Die Person
-Klasse ist nun vollständig unabhängig von der Implementierung der Wettervorhersage. Änderungen an der Implementierung können
einfach in der WeatherModule
-Klasse vorgenommen werden.
Erweiterung des DI-Containers
Es ist auch möglich, zusätzliche Abhängigkeiten oder Implementierungen zum Injector hinzuzufügen, indem man den Module
-Klassen
weitere @provider
-Methoden hinzufügt.
Häufig gestellte Fragen zur Dependency Injection
Wie unterscheidet sich Dependency Injection in Python von C#?
Python ist dynamisch typisiert und verwendet keine formalen Interfaces. Stattdessen werden Abstraktionen oft über Basis- oder abstrakte
Klassen implementiert. Bibliotheken wie injector
ermöglichen jedoch ähnliche DI-Funktionen wie in statisch typisierten Sprachen wie C#.
Warum sollte ich injector
statt manueller Dependency Injection verwenden?
Die Verwendung eines DI-Containers wie injector
ermöglicht es, Abhängigkeiten zentral zu verwalten und macht den Code modularer und
flexibler. Manuelles Verwalten von Abhängigkeiten ist fehleranfällig und erschwert die Erweiterung des Codes.
Fazit
Dependency Injection in Python mit der Bibliothek injector
hilft, den Code modularer, flexibler und leichter erweiterbar zu gestalten.
Durch die Verwendung eines DI-Containers können Abhängigkeiten automatisiert verwaltet werden, was die Implementierung einfacher und
effizienter macht.