Show Dependency Inversion with a NotificationService that currently creates EmailSender inside itself.
Define a Sender interface, have NotificationService depend on it, and pass the implementation in from outside. The service no longer knows whether it is sending email, SMS or WhatsApp.
Explain it simply
A table lamp that has its power cable welded into one particular wall socket is hard to move. A plug and a standard socket let it work anywhere. Directly writing EmailSender() inside the service is the welded cable; an interface is the standard socket.
Example
# Before: high-level class locked to a low-level one
class NotificationService:
def __init__(self):
self.sender = EmailSender() # hard-wired
# After: depends on an abstraction, receives it from outside
class Sender:
def send(self, to, text): raise NotImplementedError
class EmailSender(Sender):
def send(self, to, text): print(f"mail to {to}: {text}")
class SmsSender(Sender):
def send(self, to, text): print(f"sms to {to}: {text}")
class NotificationService:
def __init__(self, sender: Sender):
self.sender = sender
def notify(self, to, text):
self.sender.send(to, text)
NotificationService(SmsSender()).notify("98xxxxxx10", "OTP 4821")
# sms to 98xxxxxx10: OTP 4821In tests, pass a fake Sender that stores messages in a list. No real mail goes out.
What to say in the interview
"I introduce a Sender abstraction, make NotificationService depend on it, and inject the implementation, usually through the constructor. That decouples the service from the channel and makes it easy to swap and to test."
Common mistakes
- Creating an interface that mirrors only one implementation and exposes its details.