编程中的抽象是什么?
在编程中,抽象(abstraction)指把某样东西的实现细节藏在一个更简单的接口后面,让你只需知道它做什么就能使用它。调用 sorted(names) 就能给列表排序,你不必编写排序算法,甚至看都不用看一眼。
更新于 2026年9月24日
当你写下 print("hi") 时,你不会去想这几个字母是怎么出现在屏幕上的。在这一行代码之下,Python 先把文本转换成字节,再请求操作系统把它们写出去;操作系统把字节交给终端,终端再用字体把每个字母绘制成像素。每一层都向上一层提供一个简单的承诺,同时隐藏自己是如何兑现这个承诺的。这种隐藏就是抽象,而每个程序都建立在许多这样的层之上。
抽象的层次
| 层次 | 你操作的对象 | 它隐藏了什么 |
|---|---|---|
| 你的程序 | print("hi")、sorted(names) | 库是如何完成工作的 |
| Python 及其标准库 | 字符串、列表、文件 | 内存管理、系统调用 |
| 解释器(CPython) | 字节码 | 运行字节码的 C 代码和机器码 |
| 操作系统 | 文件、进程 | 磁盘块、设备驱动、CPU 调度 |
| 硬件 | 机器指令 | 晶体管和电信号 |
每一层都由下一层构建而成,但不了解下一层也能理解它。你不知道磁盘块是什么,照样能写出能运行的 Python 代码;操作系统也不需要知道文件里装的是 Python 代码,照样能把它存好。
代码中的抽象是如何实现的
程序员主要用两种方式构建自己的抽象层。
过程抽象就是函数。函数名、参数和返回值构成接口,函数体则是被隐藏的细节。一旦有了 average(scores),程序的其他部分直接使用它即可,不必去看它是怎么相加和相除的。
数据抽象根据“能对一个类型做什么”来定义它,而不是根据它如何存储数据。队列就是“从队尾加入,从队首取出”。至于元素在内存中如何排列,则是内部自己的事:
serving soup
serving pasta
serving cake
底部的代码对 deque 一无所知。如果你把它换成普通列表加 pop(0),输出完全一样,只是队列很长时会更慢,因为删除列表的第一个元素需要移动其余所有元素。接口没有变,所以类外面的代码一行都不用改。
抽象类与接口
在面向对象编程中,抽象通常指规定一组类必须做什么,而把怎么做交给每个类自己决定。在 Python 中,abc 模块可以把方法标记为抽象方法;只要类中还有未实现的抽象方法,就不能实例化:
email to ana@example.com: Your package arrives today
sms to +15550100: Your package arrives today
Notifier is abstract: create an Email or an SMS instead
remind() 只依赖于抽象:任何带有 send 方法的东西都行。要增加推送通知,只需再写一个新类,remind() 完全不用动。最后这一步,即同一个调用对不同的类执行不同的代码,就是多态:抽象定义共同的接口,多态让它真正运转起来。
Java 用关键字提供了同样的工具:
interface Notifier {
void send(String to, String text);
}
class Email implements Notifier {
public void send(String to, String text) { /* talk to the mail server */ }
}
写 new Notifier() 会导致编译错误,因为接口没有可以运行的实现。文档详细讲解了 Java 中的抽象类和接口。
抽象与封装的区别
这两个概念经常一起出现,所以很容易混淆。
| 抽象 | 封装 | |
|---|---|---|
| 回答的问题 | 它能做什么? | 谁可以访问数据? |
| 关注点 | 接口:对外提供的操作 | 内部:接口背后的数据 |
| 主要工具 | 函数、抽象类、接口、模块 | private、_name、属性(property)、#fields |
| 示例 | Notifier.send() 同时适用于邮件和短信 | _balance 只能通过 withdraw() 修改 |
| 层面 | 设计 | 实现 |
抽象决定展示哪些操作;封装确保其余部分保持隐藏。上面的 Queue 两者兼具:add 和 next 是抽象,_items 前面的下划线则是封装。
有漏洞的抽象和其他常见错误
Joel Spolsky 在 2002 年提出了抽象泄漏定律:任何不简单的抽象,都会让一部分被隐藏的细节漏出来。浮点数是最经典的例子。它们看起来就像普通的小数,直到底层的二进制存储方式暴露出来:
0.30000000000000004
False
0.1 和 0.2 都无法用二进制位精确存储,所以两者之和会有一点偏差。你可以用好几年浮点数都不必考虑这个问题,但不可能永远不碰到它。这也是为什么了解你所在层次的下一层会很有帮助。
另外两个错误也很常见:
- 过早抽象。 为永远不会出现的情况搭建的类层次结构,只会变成额外需要阅读和修改的代码。Sandi Metz 说过:“重复的代价远比错误的抽象低。”
- 把抽象和
abstract关键字混为一谈。 抽象类只是工具之一。每个命名得当的函数也都是一种抽象。
下一步学什么
阅读封装和多态,补全你对面向对象的整体认识。操作系统页面介绍了你的程序所依赖的最大的抽象。想要动手练习,Python 课程从一开始就带你编写类和函数,Java 课程则讲解了抽象类和接口。
常见问题
抽象有哪些例子?
len(items) 时知道它返回什么,却不需要知道 Python 是怎么数的。汽车踏板是生活中的版本,你不必了解发动机如何燃烧燃料,也能控制车速。在面向对象的代码里,像 Notifier 这样只有一个 send 方法的接口,就是对邮件、短信和推送消息的抽象。Python 中的抽象是什么?
abc 模块配合 @abstractmethod 声明抽象基类;之后 Python 会拒绝为这个类创建对象,也会拒绝为任何没有实现全部抽象方法的子类创建对象。Java 编程中的抽象是什么?
new 实例化。接口列出实现它的类必须提供的方法,一个类可以同时实现多个接口。面向对象的四大特性是什么?
封装、抽象、继承和多态。封装保护对象的数据,抽象只暴露对象能做什么,继承让一个类复用另一个类的代码,多态让同一段代码通过共同的接口处理多种类型。
抽象会让代码变得更糟吗?
会,如果抽象来得太早或者藏错了东西。为永远不会出现的情况而设计的代码,会多出好几层需要阅读和修改的结构,却带不来任何好处。一个常见的经验是:等你把相似的代码写过两三遍,共同的结构已经清晰了,再给它命名。