接口与多态
引入| English | 释义 | 拼音 |
|---|---|---|
| interface/ˈɪntəfeɪs/ | 接口 | jiē kǒu |
| polymorphism/ˈpɒlɪmɔːfɪzəm/ | 多态 | duō tài |
| duck typing | 鸭子类型 | yā zi lèi xíng |
| runtime type | 运行时类型 | yùn xíng shí lèi xíng |
| dynamic dispatch | 动态绑定 | dòng tài bǎng dìng |
| open/closed | 开闭 | kāi bì |
| substitution/ˌsʌbstɪˈtjuːʃn/ | 替换 | tì huàn |
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: ...
Protocol 命名的是——
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
它是结构化契约:带有所命名方法的对象即合格,无需继承。
To be used where a Notifier Protocol is expected, a class must inherit from Notifier.
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
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.

变量持有 SmsNotify() 时执行 n.send(due),调用落到——
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
动态绑定按变量实际持有的对象解析,与标注无关。
Match each term to its meaning.
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
Together they let callers depend on behaviour, not classes.
Put the steps of the call n.send('loan due') in order.
难度 3/5 · 今日首次完成可获 200 金币及少量额外奖励
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?
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
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.
替代实现可以——
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
替换原则:至少接受所承诺的输入,绝不撕毁任何承诺。
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.