
Dependency Injection in Python
Dependency Injection is a simple but powerful principle: instead of creating dependencies inside a class, we provide them externally, usually through the constructor. This makes our code more testable, modular, and scalable.
Without Dependency Injection
In this case, the controller is tightly coupled to the service class. It creates the instance itself, which makes it harder to test or replace.
class UserController:
def __init__(self):
self.user_service = UserService() # tightly coupled
def get_user(self, user_id):
return self.user_service.find_user(user_id)
With Dependency Injection
Here, the service is passed as a parameter. This makes it easier to test with mocks or change implementations later on.
class UserController:
def __init__(self, user_service):
self.user_service = user_service
def get_user(self, user_id):
return self.user_service.find_user(user_id)
Example: Notification Service
Here, we inject a notification system so the user class doesn't need to know how the notification works internally.
class EmailNotifier:
def send(self, message):
print(f"Sending email: {message}")
class UserService:
def __init__(self, notifier):
self.notifier = notifier
def create_user(self, name):
# logic to create user...
self.notifier.send(f"User {name} created")
notifier = EmailNotifier()
user_service = UserService(notifier)
user_service.create_user("Alice")
Example: Repository Pattern with Dependency Injection
We inject a repository into a service so we can swap the database implementation without changing the service logic.
class UserRepository:
def get_user(self, user_id):
# simulate DB lookup
return {"id": user_id, "name": "John Doe"}
class UserService:
def __init__(self, repository):
self.repository = repository
def get_user_profile(self, user_id):
user = self.repository.get_user(user_id)
return f"User {user['name']} loaded"
repo = UserRepository()
service = UserService(repo)
print(service.get_user_profile(123))
Why Use Dependency Injection?
- Testability: Easily inject mock objects for testing.
- Flexibility: Swap implementations without changing core logic.
- Scalability: Better suited for growing applications.
- Single Responsibility: Classes don't manage their own dependencies.
Key Takeaway
Dependency Injection is about separating concerns. It's not about complexity — it's about control. You decide what your class needs, and you pass it in, instead of creating it inside.