Abstraction with interfaces
Depend on what something can do, not on what it is. Then the implementation can be swapped without touching the caller.
Watch it animatedStep through it, with the running line highlighted. Free with an account.The idea
An interface is a contract: a list of members with no implementation. OrderService only knows the contract. Whoever creates it decides which real notifier to hand in.
Code
class Notifier { // an interface: only pure virtual functions
public:
virtual void send(const string& message) = 0;
virtual ~Notifier() = default;
};
class EmailNotifier : public Notifier {
public:
void send(const string& message) override { cout << "Email: " << message << "\n"; }
};
class SmsNotifier : public Notifier {
public:
void send(const string& message) override { cout << "SMS: " << message << "\n"; }
};
class OrderService {
Notifier& notifier;
public:
OrderService(Notifier& notifier) : notifier(notifier) {}
void place() { notifier.send("Order placed"); }
};
int main() {
EmailNotifier email; SmsNotifier sms;
OrderService(email).place();
OrderService(sms).place();
}from typing import Protocol
class Notifier(Protocol):
def send(self, message: str) -> None: ...
class EmailNotifier:
def send(self, message: str) -> None: print("Email:", message)
class SmsNotifier:
def send(self, message: str) -> None: print("SMS:", message)
class OrderService:
def __init__(self, notifier: Notifier):
self.notifier = notifier
def place(self) -> None: self.notifier.send("Order placed")
OrderService(EmailNotifier()).place()
OrderService(SmsNotifier()).place()interface INotifier {
void Send(string message);
}
class EmailNotifier : INotifier {
public void Send(string message) => Console.WriteLine("Email: " + message);
}
class SmsNotifier : INotifier {
public void Send(string message) => Console.WriteLine("SMS: " + message);
}
class OrderService {
private readonly INotifier notifier;
public OrderService(INotifier notifier) { this.notifier = notifier; }
public void Place() => notifier.Send("Order placed");
}
new OrderService(new EmailNotifier()).Place();
new OrderService(new SmsNotifier()).Place();Tips
- Handing a dependency in through the constructor is called dependency injection.
- In a unit test you can pass a fake INotifier that just records the message, so no email is sent.
In the .NET library
ASP.NET Core is built around this: builder.Services.AddScoped<INotifier, EmailNotifier>() registers the choice once, and the framework injects it into every constructor that asks for INotifier.