Dependency Injection
Definition: Dependency Injection (DI) is a design pattern used in object-oriented programming where an object’s dependencies (i.e., the objects it relies on to function) are injected into it, rather than the object creating these dependencies itself. This promotes loose coupling, enhances testability, and makes it easier to manage and maintain the code.
Example in Python
Consider a scenario where you have a service that sends notifications. This service can send notifications via email or SMS.
- Without Dependency Injection:
1class EmailService:
2 def send_email(self, message):
3 print(f"Sending email with message: {message}")
4
5class SMSService:
6 def send_sms(self, message):
7 print(f"Sending SMS with message: {message}")
8
9class NotificationService:
10 def __init__(self):
11 self.email_service = EmailService()
12 self.sms_service = SMSService()
13
14 def notify(self, message, method):
15 if method == "email":
16 self.email_service.send_email(message)
17 elif method == "sms":
18 self.sms_service.send_sms(message)
19
20notification_service = NotificationService()
21notification_service.notify("Hello, World!", "email")
22notification_service.notify("Hello, World!", "sms")
In this example, NotificationService
directly creates instances of EmailService
and SMSService
. This makes the NotificationService
class tightly coupled to EmailService
and SMSService
, making it harder to test and maintain.
- With Dependency Injection:
1class EmailService:
2 def send_email(self, message):
3 print(f"Sending email with message: {message}")
4
5class SMSService:
6 def send_sms(self, message):
7 print(f"Sending SMS with message: {message}")
8
9class NotificationService:
10 def __init__(self, email_service, sms_service):
11 self.email_service = email_service
12 self.sms_service = sms_service
13
14 def notify(self, message, method):
15 if method == "email":
16 self.email_service.send_email(message)
17 elif method == "sms":
18 self.sms_service.send_sms(message)
19
20email_service = EmailService()
21sms_service = SMSService()
22notification_service = NotificationService(email_service, sms_service)
23notification_service.notify("Hello, World!", "email")
24notification_service.notify("Hello, World!", "sms")
In this version, the NotificationService
class no longer creates instances of EmailService
and SMSService
itself. Instead, these dependencies are injected into it from the outside. This approach has several benefits:
- Loose Coupling:
NotificationService
does not depend on the concrete implementations ofEmailService
andSMSService
. It depends on their interfaces (in this case, the methods they provide). - Easier Testing: You can easily replace
EmailService
andSMSService
with mock objects when testingNotificationService
. - Flexibility: You can change the implementations of
EmailService
andSMSService
without modifyingNotificationService
.
Summary
Dependency Injection helps in creating more modular, testable, and maintainable code by decoupling the dependencies from the class that uses them. This is achieved by injecting the dependencies from outside rather than letting the class create them internally.