前言
”面向接口编程“写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性、维护性谁都不能拒绝。
最近无意间看到了我刚开始写 Python 时的部分代码,当时实现的需求有个很明显的特点:
- 不同对象具有公共的行为能力,但具体每个对象的实现方式又各不相同。
说人话就是商户需要接入平台,接入的步骤相同,但具体实现不同。
作为一个”资深“ Javaer,需求还没看完我就洋洋洒洒的把各个实现类写好了:
当然最终也顺利实现需求,甚至把组里一个没写过 Java 的大哥唬的一愣一愣的,直呼牛逼。
不过事后也给我吐槽:
- 你这设计是不错,但是感觉好复杂,跟代码时要找到真正的业务逻辑(实现类)得绕几圈。
截止目前 Python 写多了,我总算是能总结他的感受:就是不够 Pythonic。
虽说 Python 没有类似 Java 这样的 Interface 特性,但作为面向对象的高级语言也是支持继承的;
在这里我们也可以利用继承的特性来实现面向接口编程:
class Car: def run(self): pass class Benz(Car): def run(self): print("benz run") class BMW(Car): def run(self): print("bwm run") def run(car): car.run() if __name__ == "__main__": benz = Benz() bmw = BMW() run(benz) run(bmw)
代码非常简单,在 Python 中也没有类似于 Java 中的 extends 关键字,只需要在类声明末尾用括号包含基类即可。
这样在每个子类中就能单独实现业务逻辑,方便扩展和维护。
类型检查
由于 Python 作为一个动态类型语言,无法做到 Java 那样在编译期间校验一个类是否完全实现了某个接口的所有方法。
为此 Python 提供了解决办法,那就是 abc(Abstract Base Classes) ,当我们将基类用 abc 声明时就能近似做到:
import abc class Car(abc.ABC): @abc.abstractmethod def run(self): pass class Benz(Car): def run(self): print("benz run") class BMW(Car): pass def run(car): car.run() if __name__ == "__main__": benz = Benz() bmw = BMW() run(benz) run(bmw)
一旦有类没有实现方法时,运行期间便会抛出异常:
bmw = BMW()
TypeError: Can't instantiate abstract class BMW with abstract methods run
虽然无法做到在运行之前(毕竟不需要编译)进行校验,但有总比没有好。
鸭子类型
以上两种方式看似已经毕竟优雅的实现面向接口编程了,但实际上也不够 Pythonic。
在继续之前我们先聊聊接口的本质到底是什么?
在 Java 这类静态语言中面向接口编程是比较麻烦的,也就是我们常说的子类向父类转型,因此需要编写额外的代码。
带来的好处也是显而易见,只需要父类便可运行。
但我们也不必过于执着于接口,它本身只是一个协议、规范,并不特指 Java 中的 Interface,甚至有些语言压根没有这个关键字。
动态语言的特性也不需要强制校验是否实现了方法。
在 Python 中我们可以利用鸭子类型来优雅的实现面向接口编程。
在这之前先了解下鸭子类型,借用维基百科的说法:
- “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我用大白话翻译下就是:
即便两个完全不想干的类,如果他们都实现了相同的方法,那就可以把他们当做同一类型的类来使用。
举个简单例子:
class Order: def create(self): pass class User: def create(self): pass def create(obj): obj.create() if __name__ == "__main__": order = Order() user = User() create(order) create(user)
这里的 order 和 user 本身完全没有关系,只是他们都有相同方法,又得益于动态语言没法校验类型的特点,所以完全可以在运行的时候认为他们是同一种类型。
因此基于鸭子类型,之前的代码我们可以稍作简化:
class Car: def run(self): pass class Benz: def run(self): print("benz run") class BMW: def run(self): print("bwm run") def run(car): car.run() if __name__ == "__main__": benz = Benz() bmw = BMW() run(benz) run(bmw)
因为在鸭子类型中我们在意的是它的行为,而不是他们的类型;所以完全可以不用继承便可以实现面向接口编程。
总结
我觉得平时没有接触过动态类型语言的朋友,在了解完这些之后会发现新大陆,就像是 Python 老手第一次使用 Java 时;虽然觉得语法啰嗦,但也会羡慕它的类型检查、参数验证这类特点。
动静语言之争这里不做讨论了,各有各的好,鞋好不好穿只有自己知道。
随便提一下其实不止动态语言具备鸭子类型,有些静态语言也能玩这个骚操作,感兴趣下次再介绍。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 中国武警男声合唱团《辉煌之声1天路》[DTS-WAV分轨]
- 紫薇《旧曲新韵》[320K/MP3][175.29MB]
- 紫薇《旧曲新韵》[FLAC/分轨][550.18MB]
- 周深《反深代词》[先听版][320K/MP3][72.71MB]
- 李佳薇.2024-会发光的【黑籁音乐】【FLAC分轨】
- 后弦.2012-很有爱【天浩盛世】【WAV+CUE】
- 林俊吉.2012-将你惜命命【美华】【WAV+CUE】
- 晓雅《分享》DTS-WAV
- 黑鸭子2008-飞歌[首版][WAV+CUE]
- 黄乙玲1989-水泼落地难收回[日本天龙版][WAV+CUE]
- 周深《反深代词》[先听版][FLAC/分轨][310.97MB]
- 姜育恒1984《什么时候·串起又散落》台湾复刻版[WAV+CUE][1G]
- 那英《如今》引进版[WAV+CUE][1G]
- 蔡幸娟.1991-真的让我爱你吗【飞碟】【WAV+CUE】
- 群星.2024-好团圆电视剧原声带【TME】【FLAC分轨】