Card 06/ 09
Interfaces in Python, Java and TypeScript
An ABC, a Protocol, a Java interface and a TypeScript interface all say "anything with these methods".
An interface is a type that lists the methods an object must have, without saying how they work. Most SOLID fixes introduce one, so you need to read it in the language in front of you. This card shows how Python, Java and TypeScript each declare an interface, and the one difference that matters: whether a class must name the interface it fulfils, which is called nominal typing, or merely have the right methods, which is called structural typing.
Each listing below declares a Sender with one send method, writes one class that fulfils it, and calls it through the interface. The calling code never mentions the concrete class's name, and that is the whole point.
One Sender interface in three languages
An abstract base class (ABC) from the abc module. A subclass must inherit from it and implement every abstract method, or Python refuses to create the object.
from abc import ABC, abstractmethod
class Sender(ABC):
@abstractmethod
def send(self, to: str, body: str) -> None: ...
class EmailSender(Sender):
def send(self, to: str, body: str) -> None:
print(f"Email to {to}: {body}")
class Unfinished(Sender):
pass
EmailSender().send("[email protected]", "Order shipped")
try:
Unfinished()
except TypeError as error:
print(error)Email to [email protected]: Order shipped
Can't instantiate abstract class Unfinished without an implementation for abstract method 'send'A Protocol from the typing module. SmsSender never mentions Sender; a type checker accepts it because it has the right method. Python itself does not check this at run time.
from typing import Protocol
class Sender(Protocol):
def send(self, to: str, body: str) -> None: ...
class SmsSender:
def send(self, to: str, body: str) -> None:
print(f"SMS to {to}: {body}")
def tell_customer(sender: Sender) -> None:
sender.send("07700 900123", "Order shipped")
tell_customer(SmsSender())SMS to 07700 900123: Order shippedA Java interface. The class must say implements Sender, and the compiler checks every method.
public class IfaceJava {
static void tellCustomer(Sender sender) {
sender.send("[email protected]", "Order shipped");
}
public static void main(String[] args) {
tellCustomer(new EmailSender());
}
}
interface Sender {
void send(String to, String body);
}
class EmailSender implements Sender {
public void send(String to, String body) {
System.out.println("Email to " + to + ": " + body);
}
}Email to [email protected]: Order shippedA TypeScript interface. PushSender never names it; the compiler accepts it because the shape matches.
interface Sender {
send(to: string, body: string): void;
}
class PushSender {
send(to: string, body: string): void {
console.log(`Push to ${to}: ${body}`);
}
}
function tellCustomer(sender: Sender): void {
sender.send("device-42", "Order shipped");
}
tellCustomer(new PushSender());Push to device-42: Order shippedRemove implements Sender from a Java class and call it the same way, and the build stops with SmsSender cannot be converted to Sender. Java only trusts what a class declares. TypeScript and a Python Protocol trust what a class has.
| Declaration | Class must name it | Checked when |
|---|---|---|
| Python ABC | Yes, by inheriting | At run time, when the object is created |
| Python Protocol | No, structural | Only by a type checker such as mypy |
| Java interface | Yes, with implements | At compile time |
| TypeScript interface | No, structural | At compile time |
For SOLID the difference is mostly about convenience. A structural interface lets you fit an existing class, even one from a library, without editing it. A nominal one makes the intent visible on the class itself. Either way the high-level code depends on Sender, not on any concrete sender.
← → move through sections and cards