前言

”面向接口编程“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)

这里的 orderuser 本身完全没有关系,只是他们都有相同方法,又得益于动态语言没法校验类型的特点,所以完全可以在运行的时候认为他们是同一种类型。

因此基于鸭子类型,之前的代码我们可以稍作简化:

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 时;虽然觉得语法啰嗦,但也会羡慕它的类型检查、参数验证这类特点。

动静语言之争这里不做讨论了,各有各的好,鞋好不好穿只有自己知道。

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

Python 中的面向接口编程的更多相关文章

  1. Java中的面向接口编程

    面向接口编程是很多软件架构设计理论都倡导的编程方式,学习Java自然少不了这一部分,下面是我在学习过程中整理出来的关于如何在Java中实现面向接口编程的知识.分享出来,有不对之处还请大家指正. 接口体 ...

  2. javascript设计模式学习之十七——程序设计原则与面向接口编程

    一.编程设计原则 1)单一职责原则(SRP): 这里的职责是指“引起变化的原因”:单一职责原则体现为:一个对象(方法)只做一件事. 事实上,未必要在任何时候都一成不变地遵守原则,实际开发中,因为种种原 ...

  3. Java面向接口编程,低耦合高内聚的设计哲学

    接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极大的降低程序中各个模块之间的耦合,提高系统的可维护性以及可扩展性. 因此,很多的软件架构设计理念都倡导"面向接口编程"而 ...

  4. java接口,接口的特性,接口实现多态,面向接口编程

    package cn.zy.cellphone; /**接口是一种引用数据类型.使用interface声明接口,形式 * 形式:public interface 接口名称{} * 接口不能拥有构造方法 ...

  5. java面向接口编程

    在oop中有一种设计原则是面向接口编程,面向接口编程有非常多优点,详细百度一大片.我来谈一下详细的使用中的一些不成熟的见解.! 首先面向接口编程能够消除类之间的依赖关系,使得业务仅仅依赖接口. 这样有 ...

  6. go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?

    到底是要猫还是要狗 在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念. ...

  7. [译]如何在ASP.NET Core中实现面向切面编程(AOP)

    原文地址:ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE 原文作者:ZANID HAYTAM 译文地址:如何在ASP.NET Cor ...

  8. 【GoLang】golang 面向对象编程 & 面向接口编程

    005.面向对象&接口编程 1 面向函数编程 1.1 将数据作为参数传递到函数入参 1.2 对象与函数是分离的 2 面向对象编程 2.1 使用者看起来函数作为对象的属性而非参数 2.2 函数属 ...

  9. C# 中使用面向切面编程(AOP)中实践代码整洁

    1. 前言 最近在看<架构整洁之道>一书,书中反复提到了面向对象编程的 SOLID 原则(在作者的前一本书<代码整洁之道>也是被大力阐释),而面向切面编程(Aop)作为面向对象 ...

随机推荐

  1. AcWing 311 .月之谜

    大型补档补了一年 题目链接 考虑枚举月之数的数列和,然后展开dp预处理 设当前模数为 \(P\) \(f[i][j][k]\) 表示一共有 i 位数字,数位和为 j,数值和 % P 的值为 K \(f ...

  2. stringbuilder和stringbuffer速度比较

    同样的代码,只改了类型,分别为stringbuilder和stringbuffer,只比较一下,执行引擎为hive. 当数据量为100000条,string builder耗时280秒,stringb ...

  3. SpringBoot使用Mybatis-plus及分页功能的细节部分

    pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...

  4. uni-app开发中的各种问题处理

    特别注意: ※:在components下的组件,图片路径用 /static/img/back.png  这样的根路径形式,不要用../static  或者 ../../static 的形式,不然很坑, ...

  5. MySQL锁(一)全局锁:如何做全库的逻辑备份?

    数据库锁设计的初衷是处理并发问题,这也是数据库与文件系统的最大区别. 根据加锁的范围,MySQL里大致可以分为三种锁:全局锁.表锁和行锁.接下来我们会分三讲来介绍这三种锁,今天要讲的是全局锁. 全局锁 ...

  6. 我是如何用go-zero 实现一个中台系统的

    最近发现golang社区里出了一个新星的微服务框架,来自好未来,光看这个名字,就很有奔头,之前,也只是玩过go-micro,其实真正的还没有在项目中运用过,只是觉得 微服务,grpc 这些很高大尚,还 ...

  7. vue第四单元(初识vue-在页面中直接引入vue框架-学习使用vue语法-vue的指令-介绍data用法-methods用法)

    第四单元(初识vue-在页面中直接引入vue框架-学习使用vue语法-vue的指令-介绍data用法-methods用法) #课程目标 了解 vue 框架的特点 掌握创建 vue 实例 掌握 data ...

  8. Animate..css的一些属性使用

    使用基本的代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <t ...

  9. 嵌入式开发笔记——调试组件SEGGER_RTT

    一.前言 在嵌入式开发过程中,经常会通过打印输出一些调试信息来调试参数.查找问题等,通常我的做法都是使用芯片的串口硬件设备配合串口助手软件来进行调试.但是这次项目的PCB硬件设计并未预留串口调试接口, ...

  10. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...