Whoosy's Blog

藏巧于拙 用晦而明 寓清于浊 以屈为伸

0%

如何用python实现go中面向接口编程

编码不易,转载请注意出处!

前言

最近在学习golang,发现golang中的面向接口编程写起来真的太爽了,这个思想在go、java这种语言中是非常重要的,甚至所有编程语言都需要,毕竟程序的扩展性和可维护性谁都不能拒绝。

回看了下刚开始写python的一些代码,当时实现需求有个很明显的特点:

  • 不同对象具有公共的行为能力,但具体每个对象的实现方式又各不相同。

说人话就是不同类具有相同的方法比如说createupdatedelete等,但是具体实现逻辑又都不一样。

作为一个”资深“ Pythoner,需求还没看完我就洋洋洒洒的把各个实现类写好了:

当然最终也顺利实现业务需求,甚至把组里一个刚开始写 Python 的实习生唬的一愣一愣的,直呼牛逼。

在借鉴了我这种写法之后,之后他也给我吐槽:

  • 你这设计是不错,但是感觉好复杂,跟代码时要找到真正的业务逻辑(实现类)得绕几圈。

截止目前 Python 写多了,我总算是能总结他的感受:就是不够 Pythonic。

虽说 Python 没有类似 Java 这样的 Interface 特性,但作为面向对象的高级语言也是可以变相支持的;

在这里我们也可以利用面向对象中继承和多台的特性来实现面向接口编程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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作为一个动态类型语言,无法做到 Go 那样在编译期间校验一个类是否完全实现了某个接口的所有方法;
为此 Python 提供了解决办法,那就是 abc(Abstract Base Classes) ,当我们将基类用 abc 声明时就能近似做到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

一旦有类没有实现方法时,运行期间便会抛出异常:

1
2
bmw = BMW()
TypeError: Can't instantiate abstract class BMW with abstract methods run

虽然无法做到在运行之前(毕竟不需要编译)进行校验,但有总比没有好。

鸭子类型

以上两种方式虽然已经实现面向接口编程了,但实际上也不够Pythonic

在 Python 中我们可以利用鸭子类型来更优雅的实现面向接口编程。

在这之前先了解下什么是鸭子类型,借用维基百科的说法:

  • “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

对应到程序来讲:即便两个完全不想干的类,如果他们都实现了相同的方法,那就可以把他们当做同一类型的类来使用。

来简单举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Order:
def create(self):
pass
def delete(self):
pass

class User:
def create(self):
pass
def delete(self):
pass

def create(obj):
obj.create()

def delete(obj):
obj.delete()

if __name__ == "__main__":
order = Order()
user = User()
create(order)
create(user)
delete(order)
delete(user)

这里的Order类和User类本身完全没有关系,只是他们都有相同方法createdelete,又得益于动态语言只能在运行的时候校验类型,所以在编写代码的时候完全可以认为他们是同一种类型。

因为在鸭子类型中我们在意的是它的行为是否一样,而不是他们的类型;所以完全可以不用继承便可以实现面向接口编程。

总结

我觉得平时没有接触过动态类型语言的朋友,在了解完这些之后会发现新大陆,就像是Python老手第一次使用JavaGo时;虽然觉得语法啰嗦,但也会羡慕它的类型检查、参数验证这类特点。

随便提一下其实不止动态语言具备鸭子类型,有些静态语言也能玩这个骚操作,感兴趣下次再介绍。