Card 04/ 07
SOLID in Python vs Java and TypeScript
Duck typing makes some interfaces optional; declared types make them visible.
The SOLID principles apply in every object-oriented language, but they look different depending on how a language decides that an object fits an interface. This card compares Python with Java and TypeScript, principle by principle, and ends on when a Python class should declare an interface anyway.
Java uses nominal typing: a class fits an interface only if it says implements. TypeScript uses structural typing: a value fits if it has the right shape, checked by the compiler. Python uses duck typing: nothing is checked before the program runs, and an object fits if it has the method when the method is called. Python can also declare interfaces, with abstract base classes (ABCs) or typing.Protocol, which a type checker such as mypy checks by shape.
| Principle | Python | Java | TypeScript |
|---|---|---|---|
| Single responsibility | Modules and functions are natural seams, so not every responsibility needs a class. | At most one public top-level class per file, so a split means a new class. | Modules and functions as in Python, classes where state is shared. |
| Open/closed | A new class or function passed in; a dictionary of strategies is common. | A new class implementing a declared interface. | A new class or object literal matching an interface. |
| Liskov substitution | Nothing checks signatures before run time; a type checker catches some breaches. | The compiler checks signatures, not behaviour. | The compiler checks shapes, not behaviour. |
| Interface segregation | Small Protocols, or no declaration at all for a single method. | Small interfaces; a class implements several. | Small interfaces, often combined with & or extends. |
| Dependency inversion | Any object with the right method can be passed in; a Protocol documents what is expected. | The constructor parameter must name an interface, or the dependency stays concrete. | As Java, but any matching object can be passed without implements. |
The row that differs most is dependency inversion. In Python, a function that calls .write(text) on whatever it is given already depends on a behaviour rather than a class; nothing extra is needed for the dependency to be replaceable. In Java the same function must name a type, and if that type is a concrete class, the dependency is concrete.
So when should a Python class declare an ABC or a Protocol? Declare one when the interface is a boundary other people implement, such as a plugin or a storage adapter, so that the expected methods are written down in one place. Declare one when you run a type checker, so that a missing method is caught before the program runs. Prefer Protocol when implementers should not have to import or inherit anything, and an ABC when you want instantiation to fail for a class that forgot a method. For a private helper with one caller and one method, duck typing is enough.
Liskov is the same everywhere: no compiler checks behaviour. Java's checks are stricter, but an Ostrich.fly that throws passes every language's checks.
← → move through sections and cards