当前位置:首页>python>《Python 从入门到精通》079|多态:统一接口背后的设计思想

《Python 从入门到精通》079|多态:统一接口背后的设计思想

  • 2026-09-11 01:06:34
《Python 从入门到精通》079|多态:统一接口背后的设计思想

查理·芒格:

人一辈子做对两件事就可以很富有:寻找什么是有效的,重复它;发现什么是无效的,避免它。

上一章我们讲了继承。你已经知道,子类可以复用父类的能力,也可以在父类基础上继续扩展自己的属性和方法。那再往前走一步,就会碰到一个非常关键的思想:多态。

很多人第一次听到“多态”这两个字,会本能觉得抽象、难、像考试题。其实你把它翻译成人话,它一点都不玄。

多态,说白了就是:

同样的调用方式,落到不同对象身上,可以表现出不同的行为。

注意这句话,非常关键。 不是“方法名不同,行为不同”,那不叫多态。 真正的多态是:

方法名可以一样 调用方式也一样 但对象不同,执行结果不同

这背后体现出来的,就是“统一接口”的设计思想。

一、先别急着背定义,先看现实世界

比如你按下遥控器上的“开机”按钮。

对电视来说,是开电视。 对空调来说,是开空调。 对投影仪来说,是开投影仪。

你按的动作是不是同一个?是。 接口是不是统一的?也是。 但不同设备接到这个动作后,执行出来的结果不一样。

这就是非常典型的多态味道。

再比如你点一个播放器上的“播放”按钮。

播放音乐软件,会播歌。 播放视频软件,会放视频。 语音播放器,可能会播录音。

外部操作是统一的。 内部行为由具体对象自己决定。

这就是多态最接地气的理解方式。

二、为什么多态会在继承后讲

因为多态通常离不开一个前提:

先有一组“同一类”的对象, 它们共享一个相对统一的接口, 然后每个子类再给出自己的实现。

也就是说,多态和继承经常配合出现。

比如:

Animal 是父类Dog、Cat、Bird 是子类

父类里先约定一个方法:

speak()

然后各个子类自己去实现:

狗 speak() 是汪汪汪 猫 speak() 是喵喵喵 鸟 speak() 是啾啾啾

这时候,你外部只需要统一调用:

对象.speak()

至于到底发出什么声音,由具体对象自己决定。

这,就是多态。

三、先看一个最典型的多态例子

classAnimal:defspeak(self):        print('动物发出了声音')classDog(Animal):defspeak(self):        print('汪汪汪')classCat(Animal):defspeak(self):        print('喵喵喵')classBird(Animal):defspeak(self):        print('啾啾啾')

创建对象:

dog1 = Dog()cat1 = Cat()bird1 = Bird()dog1.speak()cat1.speak()bird1.speak()

输出结果:

汪汪汪喵喵喵啾啾啾

这段代码特别重要,你一定要看懂。

表面上看,调用方式完全一样:

对象.speak()

但不同对象调用后,结果不同。

这正是多态的核心。

同一个接口,多个不同实现。

四、这里的关键,不是对象不同,而是“调用方式统一”

这一点要单独强调。

因为如果你写成这样:

dog1.bark()cat1.meow()bird1.chirp()

虽然结果也不同,但这不能很好体现多态。

为什么?

因为外部使用方式不统一。 你每处理一种对象,都得记不同的方法名。

而多态更强调的是:

外部只认统一动作 内部由对象自己决定怎么完成

所以多态真正厉害的地方,不是“不同对象会做不同事”,而是:

不同对象可以在同一个接口下,做出不同反应。

这会让程序结构特别灵活。

五、统一接口,到底有什么价值

这是比定义更重要的问题。

统一接口最大的价值,是让外部代码更简单、更通用。

比如你有一个函数,想让传进来的动物都发出声音。

如果没有多态,你可能得这样写:

if isinstance(animal, Dog):    animal.bark()elif isinstance(animal, Cat):    animal.meow()elif isinstance(animal, Bird):    animal.chirp()

这种写法的问题非常明显。

第一,分支越来越多。 第二,每增加一种新动物,都得回来改这里。 第三,外部代码被迫知道太多细节。

但如果有多态,你就可以写成这样:

defmake_animal_speak(animal):    animal.speak()

然后:

make_animal_speak(Dog())make_animal_speak(Cat())make_animal_speak(Bird())

这就清爽太多了。

因为外部只要求:

你得会 speak()

至于你具体怎么叫,我不管。 这就是统一接口的强大之处。

六、这其实是在把“变化”关进对象内部

你可以这样理解多态的本质价值:

外部不需要知道每个对象的具体细节。 外部只需要相信,它们都支持同一个接口。

变化放在哪里?

放在每个对象自己内部。

比如:

狗自己决定怎么 speak()猫自己决定怎么 speak()鸟自己决定怎么 speak()

而外部世界只负责统一调用。

这其实和前面讲封装有点一脉相承:

封装,是隐藏内部细节。 多态,是在隐藏细节的基础上,进一步让外部只面对统一接口。

所以你会发现,面向对象这些概念不是零散的,它们其实是互相咬合的。

七、再看一个更像业务开发的例子

假设你在做一个支付系统。

你可能会有:

支付宝支付 微信支付 银行卡支付

如果没有多态,外部逻辑可能会变成这样:

if pay_type == 'alipay':    调用支付宝逻辑elif pay_type == 'wechat':    调用微信逻辑elif pay_type == 'bank':    调用银行卡逻辑

一开始看还行。 但只要支付方式一多,这种写法就会越来越臃肿。

更自然的思路是先定义一个统一接口:

pay()

然后不同支付类各自实现自己的 pay()。

例如:

classPayment:defpay(self, amount):        print('执行支付')classAlipay(Payment):defpay(self, amount):        print(f'使用支付宝支付了 {amount} 元')classWechatPay(Payment):defpay(self, amount):        print(f'使用微信支付了 {amount} 元')classBankPay(Payment):defpay(self, amount):        print(f'使用银行卡支付了 {amount} 元')

外部只需要写:

defprocess_payment(payment, amount):    payment.pay(amount)

调用时:

process_payment(Alipay(), 100)process_payment(WechatPay(), 200)process_payment(BankPay(), 300)

输出结果:

使用支付宝支付了 100 元使用微信支付了 200 元使用银行卡支付了 300 元

你看,这就是多态在真实业务里非常常见的样子。

八、多态不是“看上去高级”,而是“减少判断分支”

这一点特别值得你抓住。

很多新手第一次学多态时,会把注意力放在“哇,一个方法名能这样用,好神奇”。 其实真正值钱的不是“神奇”,而是它带来的代码结构优化。

多态最大的实际价值之一,就是:

把大量 if...elif...else 分支,转化成统一调用。

这会带来两个直接好处:

代码更短 扩展更方便

以后你新增一个 ApplePay,只要新写一个类并实现 pay(),外部 process_payment() 函数几乎不用改。

这就是多态真正的工程价值。

九、父类在多态里扮演什么角色

父类很多时候像是在定义一份“统一规范”。

比如:

动物类约定都有 speak()支付类约定都有 pay()图形类约定都有 draw()员工类约定都有 work()

注意,父类不一定要把这个方法写得很复杂。 有时候它甚至只是先留一个通用接口位置。

真正关键的是:

子类都遵守这个接口规则,并各自给出自己的实现。

所以你可以把父类在多态里的作用理解成:

先把“大家都该会什么”定下来。

子类再去回答:

我具体怎么做。

十、方法重写,是多态成立的关键基础之一

前一章我们已经接触过方法重写。

比如:

classAnimal:defspeak(self):        print('动物发出了声音')classDog(Animal):defspeak(self):        print('汪汪汪')

这里的 Dog.speak(),就是在重写父类的 speak()。

为什么多态离不开它?

因为如果所有子类都直接照搬父类实现,那统一接口就统一了,但“不同表现”这件事就没发生。 多态的灵魂恰恰是:

接口相同,实现不同。

而实现不同,通常就是靠子类去重写父类方法。

所以你可以简单记:

继承提供共同接口 重写提供不同实现 两者合起来,就很容易形成多态

十一、再看一个图形绘制的例子

这个例子也非常经典。

classShape:defdraw(self):        print('开始绘图')classCircle(Shape):defdraw(self):        print('正在画一个圆形')classRectangle(Shape):defdraw(self):        print('正在画一个矩形')classTriangle(Shape):defdraw(self):        print('正在画一个三角形')

统一调用:

defdraw_shape(shape):    shape.draw()draw_shape(Circle())draw_shape(Rectangle())draw_shape(Triangle())

输出结果:

正在画一个圆形正在画一个矩形正在画一个三角形

你会发现,draw_shape() 函数根本不需要知道传进来的是圆、矩形还是三角形。 它只需要知道:

你这个对象支持 draw() 就行。

这就是多态在“提高通用性”这件事上的威力。

十二、多态让“写代码的人”和“扩展代码的人”更容易协作

这是一个稍微偏工程化、但非常真实的价值。

比如有一个公共函数:

defprocess_payment(payment, amount):    payment.pay(amount)

这个函数的作者,其实不用知道未来所有支付方式。 他只需要规定:

支付对象必须有 pay(amount) 方法

以后别人要扩展新的支付方式,只要写一个新类并遵守这个接口就行。 原来的公共函数不需要改。

这意味着:

写框架的人负责定义接口 扩展功能的人负责实现接口

这就是多态特别适合大型项目的原因之一。

十三、你可以把多态理解成“同一命令,不同响应”

这个翻译非常好用。

比如:

speak()pay()draw()work()

这些都像是统一命令。

而不同对象收到命令后的响应,可以不一样。

狗收到 speak(),汪汪汪 猫收到 speak(),喵喵喵 鸟收到 speak(),啾啾啾

支付宝收到 pay(),走支付宝流程 微信收到 pay(),走微信流程 银行卡收到 pay(),走银行卡流程

只要你脑子里一直抓住“统一命令,不同响应”,多态就不会再抽象。

十四、Python 里的多态,并不一定强依赖父类

这里先给你一个稍微进阶一点、但很重要的认知。

在很多语言里,多态常常更强调严格的继承体系。 但 Python 本身非常灵活,它更强调“你有没有这个方法”,而不一定强制要求你必须来自某个父类。

比如:

classDog:defspeak(self):        print('汪汪汪')classCat:defspeak(self):        print('喵喵喵')

然后:

defmake_it_speak(obj):    obj.speak()

调用:

make_it_speak(Dog())make_it_speak(Cat())

照样能跑。

因为 Python 更关注:

这个对象有没有 speak() 方法

而不是死抠:

它是不是某个父类的子类

这种思想以后你会听到一个词,叫“鸭子类型”。不过这一章先不展开,只要你先知道:

Python 里的多态,既可以借助继承,也可以借助统一方法约定。

但在入门阶段,先从“继承加方法重写”这条线理解最稳。

十五、统一接口,为什么能让未来扩展更容易

因为外部代码写一次就够了。

比如:

defdraw_shape(shape):    shape.draw()

以后你新增 Hexagon、Star、HeartShape,只要这些类都实现了 draw(),这个函数完全不用改。

这就是非常典型的“对扩展开放,对修改关闭”的味道。你现在不用背这个术语,但可以先感受一下:

好的设计,不是每来一个新需求就回头大改旧代码,而是新对象接进来就能用。

多态恰恰特别擅长做到这一点。

十六、一个常见误区:以为方法名一样就自动算多态

不完全对。

如果只是方法名一样,但外部根本没有在统一接口下使用它们,那多态价值并没有真正体现出来。

例如:

dog.speak()cat.speak()bird.speak()

这当然已经有多态味道了。 但真正更体现设计思想的,是把它们放进统一流程里,比如:

animals = [Dog(), Cat(), Bird()]for animal in animals:    animal.speak()

或者:

defmake_all_speak(animal_list):for animal in animal_list:        animal.speak()

这时候多态的价值才真正冒出来。 因为你开始利用统一接口处理一组不同对象了。

所以别只盯着“方法名一样”,更要看“是不是在统一调用”。

十七、多态和封装、继承的关系,可以这样串起来

这一章其实很适合帮你把前面三章再串一下。

封装,让每个对象把自己的数据和实现细节管起来。 继承,让子类复用父类的公共能力。 多态,让外部在统一接口下使用不同对象,而不必关心每个对象内部到底怎么实现。

你会发现,这三者其实是在一层层往上搭。

先让对象自己成型 再让一类对象建立父子结构 最后让外部统一调用它们

这就是面向对象越来越有味道的地方。

十八、一个完整综合案例

下面我们做一个稍微完整一点的小例子。

classEmployee:defwork(self):        print('员工正在工作')classProgrammer(Employee):defwork(self):        print('程序员正在写代码')classDesigner(Employee):defwork(self):        print('设计师正在画设计稿')classProductManager(Employee):defwork(self):        print('产品经理正在写需求文档')

统一调用函数:

defstart_work(employee):    employee.work()

调用:

start_work(Programmer())start_work(Designer())start_work(ProductManager())

输出结果:

程序员正在写代码设计师正在画设计稿产品经理正在写需求文档

这个例子特别像真实团队协作。

外部系统只管“安排开始工作”。 具体怎么工作,由不同角色自己决定。

这就是多态在业务建模里的典型应用。

十九、什么时候你应该开始想到多态

当你发现下面这种味道时,就可以开始想到它了:

有多个对象 它们本质上属于同一类体系 它们都应该支持某个统一动作 但具体实现方式各不相同

这时候,多态往往就是很合适的设计思路。

比如:

不同支付方式都要支付 不同图形都要绘制 不同员工都要工作 不同消息渠道都要发送 不同交通工具都要移动

只要这种“统一动作,不同实现”的模式出现,多态就很可能能派上用场。

二十、本章小练习

你可以做两个非常适合巩固的小练习。

第一个练习,定义一个 Shape 父类。 然后定义 Circle、Rectangle、Triangle 三个子类。 都重写 draw() 方法。 再写一个函数 show_shape(shape),统一调用 shape.draw()。

第二个练习,定义一个 MessageSender 父类。 然后定义 EmailSender、SmsSender、WechatSender 三个子类。 都重写 send() 方法。 再写一个函数 send_message(sender),统一调用 sender.send()。

这两个练习,一个练图形,一个练消息发送,都是多态特别经典的使用场景。

二十一、本章总结

这一章最重要的,不是记住“多态”这个术语,而是吃透它背后的设计思想。

多态的核心是:同样的调用方式,落到不同对象身上,可以表现出不同的行为。 它强调的是统一接口,而不是统一实现。 继承提供共同结构,方法重写提供不同实现,这两者经常配合形成多态。 多态最大的实际价值,是让外部代码更通用、更简洁,也更方便扩展。 它特别适合处理那种“统一动作,不同响应”的场景。 在 Python 里,多态既可以借助继承,也可以借助对象是否真的支持某个方法。

到这里,封装、继承、多态这面向对象三大核心思想,你已经都摸到门了。 下一章我们继续进入这一阶段的收束内容:080|面向对象综合案例:用类重构一个真实小项目。

最新文章

随机文章