Abstraction with interfaces

Depend on what something can do, not on what it is. Then the implementation can be swapped without touching the caller.

Time O(1)Space O(1)Code C++, Python and C#
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

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.