Interfaces and polymorphism
Đã giới thiệu| English | Ý nghĩa |
|---|---|
| interface/ˈɪntəfeɪs/ | interface |
| polymorphism/ˈpɒlɪmɔːfɪzəm/ | polymorphism |
| duck typing | duck typing |
| runtime type | runtime type |
| dynamic dispatch | dynamic dispatch |
| open/closed | open/closed |
| substitution/ˌsʌbstɪˈtjuːʃn/ | A mutation in which one nucleotide or base pair is replaced by another. |
Email today, SMS tomorrow
- The library sends due-date reminders by email. Next term it adds SMS, and later a message queue for another app.
- If the reminder code names each class, it must change every time. If it names only the behaviour "send", it never changes.
Name the behaviour
- An interface 接口 names what a thing does (
send), not which class it is. - Polymorphism 多态 is one call site running many implementations:
n.send(text)works for every kind of sender.
Duck typing and Protocol
- Python is dynamically typed: any object with the right methods fits. This is duck typing 鸭子类型.
- A
Protocolwrites the contract down without forcing inheritance, so tools can check it.
class Notifier(Protocol):
def send(self, text: str) -> str: ...
A Protocol names —
Độ khó 1/5 · 100 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
It is a structural contract: any object with the named methods fits, no inheritance needed.
To be used where a Notifier Protocol is expected, a class must inherit from Notifier.
Độ khó 1/5 · 100 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
A Protocol is structural. Any class with a matching send method fits, with no inheritance.
Dispatch follows the object
- When
n.send('loan due')runs, Python looks upsendon the runtime type 运行时类型: the class of the object, not the variable's annotation. - This is dynamic dispatch 动态绑定: the call is written once, and the method is chosen for each object at call time.

A variable holding SmsNotify() runs n.send(due): the call goes to —
Độ khó 1/5 · 100 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
Dynamic dispatch resolves on the object the variable holds, not on the annotation.
Match each term to its meaning.
Độ khó 2/5 · 140 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
Together they let callers depend on behaviour, not classes.
Put the steps of the call n.send('loan due') in order.
Độ khó 3/5 · 200 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
The variable's annotation plays no part; the object decides.
Open to new types
- Adding a fourth sender class touches no existing line: the caller was never told the concrete classes.
- This is the open/closed 开闭 idea: open to new types, closed to editing.
The reminder loop works with three sender classes. A fourth sender class is written. How many lines inside the loop must change?
Độ khó 2/5 · 140 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
The loop only calls n.send. The new class is added to the list; the loop itself is closed to edits.
Substitution is a promise
- Substitution 替换 (the Liskov rule): every implementation must accept at least what the interface promises and keep every guarantee. Preconditions may only widen; guarantees may only grow.
- A
SilentNotify.sendthat raises on an empty string narrows the contract. It breaks a caller who was told thatsendnever raises.
A substitute implementation may —
Độ khó 1/5 · 100 xu + phần thưởng nhỏ khi hoàn thành lần đầu tiên hôm nay
Substitution: accept at least what was promised, break no promise.
Replace isinstance chains
- A chain like
if isinstance(n, EmailNotify): ... elif isinstance(n, SmsNotify): ...must be edited for every new type. - Give each class its own method instead and let dispatch choose. That is what polymorphism is for.
Worked example: one loop, three senders
team = [EmailNotify(), SmsNotify(), QueueNotify()], thenfor n in team: print(n.send('loan due')).- Output:
email: loan due,sms: loan due,queue: loan due. Three classes, one call site, noif.
Three common slips
- Writing
isinstancechains. Each new type means another edit; use a method per class. - Adding a precondition in an override. Callers who trusted the interface break.
- Thinking the annotation picks the method. The object's runtime type does.
Recap
- An interface names behaviour; a Protocol writes it down; duck typing lets any fitting object in.
- Dynamic dispatch picks the method from the runtime type, so new types need no caller edits.
- Substitutes must honour the whole contract: widen inputs, keep every guarantee.