图灵奖系列 · DoggyDad 原创
Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用
Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用
ANSWER-FIRST SUMMARY
本文回答什么问题
Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用
- 主题分类:图灵奖系列
- 关键词:图灵奖、计算机历史、编程语言、算法、人工智能、数据库
- 人物实体:Barbara Liskov
图灵奖第四十三届(2008)| Barbara Liskov:她定义了”抽象数据类型”和”里氏替换原则”,让面向对象编程真正可用
一句话概括:当程序员还在纠结如何组织复杂代码时,Liskov创造了抽象数据类型(ADT)和子类型理论——她的CLU语言首次实现了模块化编程的理想,她的替换原则(LSP)成为面向对象设计的基石。
🏆 获奖简介
Barbara Liskov(芭芭拉·利斯科夫,1939-)是抽象数据类型的发明者、程序设计语言的先驱、分布式系统的奠基人。
Barbara Liskov:
-
出生时间:1939年11月7日
-
出生地点:美国加利福尼亚州洛杉矶
-
主要成就:抽象数据类型(ADT)、CLU语言、里氏替换原则(LSP)、分布式系统(Argus)
-
机构:麻省理工学院(MIT)
-
获奖年份:2008年
-
获奖原因:表彰她在实践和理论上为程序设计语言和系统设计奠定的基础,尤其是数据抽象、容错和分布式计算方面的贡献(for contributions to practical and theoretical foundations of programming language and system design, especially related to data abstraction, fault tolerance, and distributed computing)
为什么她是第四十三位? 1970年代初,软件危机肆虐——程序越来越复杂,bug越来越多,维护越来越难。问题的根源是:代码缺乏组织,数据和操作混在一起,修改一处影响全局。1972年,在MIT的Liskov提出了革命性的概念:抽象数据类型(ADT)——把数据和操作封装在一起,对外只暴露接口,隐藏实现。这个看似简单的想法,改变了编程的范式。1974-1977年,她领导设计的CLU语言首次系统化实现了ADT、异常处理、迭代器等现代概念。1987年,她提出里氏替换原则(LSP):子类型对象必须能替换父类型对象,而不破坏程序正确性——这成为面向对象设计的核心准则(SOLID的L)。1980-90年代,Liskov转向分布式系统,设计的Argus语言率先支持原子性、嵌套事务、分布式垃圾回收,为云计算和微服务奠基。从数据抽象到子类型,从语言设计到分布式系统,Liskov不仅创造了理论,更将理论转化为工程实践。今天,Java的interface、Python的类、微服务的容错机制,无一不承载着她的智慧。
🚀 Barbara Liskov的重大贡献
1. 抽象数据类型(ADT):封装的革命
问题(1970年代初):
- 代码组织混乱:数据和操作分离,难以维护
- 全局状态:任何代码都能修改任何数据
- 难以理解:不知道哪些操作合法
Liskov的洞见:
数据抽象的核心思想
传统方式(结构化编程):
{ 定义数据结构 }
type Stack = record
items: array[1..100] of integer;
top: integer;
end;
{ 操作散落在各处 }
procedure push(var s: Stack; x: integer);
begin
s.top := s.top + 1;
s.items[s.top] := x;
end;
{ 用户可以直接访问内部 }
s.top := 0; { 危险!破坏了不变式 }
Liskov的ADT方式(抽象数据类型):
stack = cluster[T: type] is create, push, pop, top, empty
rep = record[items: array[T], top: int]
create = proc() returns (cvt)
return(rep${items: array[T]$[], top: 0})
end create
push = proc(s: cvt, x: T)
s.top := s.top + 1
s.items[s.top] := x
end push
pop = proc(s: cvt) returns (T)
x: T := s.items[s.top]
s.top := s.top - 1
return(x)
end pop
{ 不变式:0 ≤ s.top ≤ size(s.items) }
end stack
关键特性:
- 封装:数据表示(rep)对外不可见
- 接口:只能通过操作(create, push, pop)访问
- 不变式:封装保证不变式总是成立
- 抽象:用户不知道(也不需要知道)内部实现
ADT的威力
例子:改变实现: 假设Stack最初用数组实现,后来改为链表:
rep = record[items: array[T], top: int] { 旧实现 }
→
rep = oneof[empty: null, node: record[val: T, next: stack[T]]] { 新实现 }
用户代码无需修改:
- 接口不变(push, pop, top)
- 内部实现自由变化
- 模块化:修改局部化
2. CLU语言:理论到实践的桥梁
1974-1977年:Liskov领导MIT团队设计CLU(CLUster)
CLU的创新特性
1. Cluster(集群):
- ADT的实现机制
- 类似今天的类(class),但更严格
- rep(representation):私有数据
- cvt(conversion):类型转换,确保封装
2. 异常处理: 问题:操作可能失败(如栈空时pop)
CLU的解决:
pop = proc(s: cvt) returns (T) signals (empty)
if s.top = 0 then signal empty end
x: T := s.items[s.top]
s.top := s.top - 1
return(x)
end pop
{ 调用者必须处理异常 }
x := stack$pop(s) except when empty: ... end
影响:
- 第一个将异常作为一等公民的语言
- 启发了Java的try-catch、Python的异常
3. 迭代器: 问题:如何遍历数据结构而不暴露内部?
CLU的yield:
elements = iter(s: cvt) yields (T)
for i: int in int$from_to(1, s.top) do
yield(s.items[i])
end
end elements
{ 使用 }
for x: int in stack$elements(s) do
... { 处理x }
end
影响:
- Python的yield
- C#的yield return
- JavaScript的iterator protocol
4. 泛型(Parametric Polymorphism): 问题:如何写通用代码?
CLU的类型参数:
stack = cluster[T: type] is ...
{ T是类型参数,可以是任何类型 }
end stack
{ 实例化 }
int_stack = stack[int]
string_stack = stack[string]
影响:
- Java的泛型(
) - C++的模板
- ML/Haskell的参数化类型
5. 多返回值:
divmod = proc(a, b: int) returns (int, int)
return(a / b, a // b) { 商和余数 }
end divmod
q, r := divmod(17, 5) { q=3, r=2 }
影响:
- Python的多返回值
- Go的多返回值
3. 里氏替换原则(LSP):子类型的正确性
1987年:Liskov与Jeannette Wing发表论文 “A Behavioral Notion of Subtyping”
问题:什么是”正确的”子类型?
场景:
class Rectangle:
def set_width(self, w): self.width = w
def set_height(self, h): self.height = h
def area(self): return self.width * self.height
class Square(Rectangle):
def set_width(self, w):
self.width = w
self.height = w # 保持正方形性质
def set_height(self, h):
self.width = h
self.height = h
测试代码:
def test(r: Rectangle):
r.set_width(5)
r.set_height(4)
assert r.area() == 20 # 期望5*4=20
Bug:
test(Rectangle()) # ✓ 通过:5*4=20
test(Square()) # ✗ 失败:4*4=16(不是20!)
问题:Square不是Rectangle的正确子类型!
Liskov替换原则(LSP)
定义:
设φ(x)是关于类型T的对象x的可证性质。那么对于类型S的对象y,如果S是T的子类型,则φ(y)应该成立。
通俗版:
- 子类型对象必须能替换父类型对象
- 替换后,程序的正确性不变
正式版(契约式设计): 子类型必须满足:
- 前置条件不强化:子类型方法的前置条件≤父类型
- 后置条件不弱化:子类型方法的后置条件≥父类型
- 不变式保持:父类型的不变式在子类型中仍成立
例子:正确的继承:
class Bird:
def move(self):
"""后置条件:位置改变"""
pass
class Sparrow(Bird): # 麻雀
def move(self):
"""飞行,满足"位置改变",OK"""
self.fly()
class Penguin(Bird): # 企鹅
def move(self):
"""游泳,满足"位置改变",OK"""
self.swim()
例子:错误的继承:
class Bird:
def fly(self):
"""后置条件:在空中"""
pass
class Penguin(Bird):
def fly(self):
"""企鹅不会飞!违反后置条件"""
raise Exception("Can't fly")
正确做法:
class Animal:
def move(self): pass
class FlyingAnimal(Animal):
def fly(self): pass
class Bird(FlyingAnimal): pass
class Penguin(Animal): pass # 不继承FlyingAnimal
LSP的影响
SOLID原则:
- S:单一职责原则
- O:开放封闭原则
- L:Liskov替换原则 ← Liskov的贡献
- I:接口隔离原则
- D:依赖倒置原则
设计指导:
- 继承要小心,不是”is-a”就该继承
- 关注行为契约,而非名称
- 组合优于继承
4. Argus:分布式编程的先驱
1980年代:Liskov转向分布式系统
挑战:分布式环境的复杂性
问题:
- 部分失败:某台机器崩溃,其他机器正常
- 并发:多个操作同时执行,冲突?
- 一致性:如何保证数据一致?
Argus语言的创新
1. 原子性(Atomicity):
概念:一组操作要么全部成功,要么全部失败
Argus的action:
action transfer(from, to: account; amount: int)
from.withdraw(amount) # 可能失败
to.deposit(amount) # 可能失败
{ 两者要么都成功,要么都不发生(回滚) }
end transfer
实现:
- 两阶段提交(2PC)
- 日志记录:用于回滚
- 锁机制:避免冲突
2. 嵌套事务(Nested Transactions):
问题:事务内部调用另一个事务怎么办?
Argus支持嵌套:
action outer()
...
action inner() # 嵌套事务
...
end inner
...
end outer
语义:
- 内层事务失败→只回滚内层
- 外层事务失败→内外层都回滚
- 灵活性:提高并发度
3. 分布式垃圾回收:
问题:
- 本地垃圾回收:容易(引用计数、标记-清除)
- 分布式:对象可能在多台机器上被引用
Argus的算法:
- 引用列表:每个对象记录哪些机器引用它
- 消息协议:引用变化时通知
- 一致性:容错处理(网络分区、机器崩溃)
影响:
- Java RMI的GC
- 分布式对象系统(CORBA、DCOM)
4. Promise(承诺):
问题:远程调用慢,等待浪费时间
Argus的call-stream:
p: promise[result] := remote_call@server() # 立即返回
... { 可以做其他事 }
res: result := p.get() { 需要时才等待 }
影响:
- JavaScript的Promise
- Python的Future
- Java的CompletableFuture
5. 理论与实践的结合
Liskov的特点:
- 不仅提理论:设计实际语言(CLU、Argus)
- 不仅写论文:构建真实系统
- 实践验证理论:CLU证明ADT可行,Argus证明分布式编程可控
影响模式:
- 识别问题:观察实践中的困难
- 提出理论:抽象、形式化
- 构建系统:实现、验证
- 迭代改进:反馈理论
🌍 对世界的深远影响
编程语言设计:ADT成为标准
CLU的直接后代:
- Java:
- class:CLU的cluster
- interface:ADT的接口
- 异常:try-catch源自CLU
- 泛型:
List<T>来自CLU
- Python:
- 迭代器:yield借鉴CLU
- 异常:try-except
- C++:
- 模板:泛型
- 异常处理
间接影响:
- 所有现代语言都有类、封装、异常
- ADT成为”常识”,但源自Liskov
面向对象设计:LSP的指导
SOLID原则:
- 软件工程师必知必会
- L是核心:保证继承正确性
设计模式:
- 策略模式:LSP保证可替换
- 模板方法:子类扩展,不破坏父类契约
代码审查:
- “这个继承满足LSP吗?”
- 成为标准检查项
分布式系统:Argus的遗产
事务处理:
- 数据库:ACID中的A(原子性)
- 分布式事务:两阶段提交
- 嵌套事务:SAP HANA、某些数据库支持
微服务:
- Saga模式:长事务的分解,类似嵌套事务
- 分布式追踪:监控分布式调用
云计算:
- AWS Lambda、Azure Functions:承诺(Promise)异步调用
- 分布式锁:Zookeeper、etcd
- 容错:重试、补偿机制
教育:影响一代程序员
MIT课程:
- Liskov教授软件工程课程数十年
- 培养大批学生,传播ADT和设计原则
教材:
- 《Program Development in Java》(Liskov著)
- 系统讲解ADT、LSP
- 全球大学使用
行业影响:
- Google、Microsoft、Facebook等公司的工程师
- 很多人的设计理念源自Liskov
🏆 获奖理由(通俗版)
ACM官方表彰:“表彰她在实践和理论上为程序设计语言和系统设计奠定的基础,尤其是数据抽象、容错和分布式计算方面的贡献。”
更通俗的理解:
如果没有Liskov:
- 我们可能仍在写”意大利面代码”
- 继承可能仍然混乱(Square继承Rectangle的bug到处都是)
- 分布式系统可能更难写
Liskov做了什么?
- 数据抽象:封装数据和操作,模块化
- LSP:定义了正确的子类型,指导OOP设计
- CLU和Argus:将理论变成可用的语言
- 分布式系统:原子性、嵌套事务、GC
本质: 她让复杂软件变得可管理,让分布式系统变得可编程。
👤 个人生平与传奇
早年与教育(1939-1968)
童年:
- 1939年:出生于加州洛杉矶
- 犹太裔家庭
- 从小对数学感兴趣
加州大学伯克利分校:
- 1961年:数学学士
- 当时计算机科学刚起步
转折点:
- 1961年:想读数学研究生
- 普林斯顿不招女生:那时还是男校(!)
- 转向计算机:机缘巧合
斯坦福大学:
- 1965年:计算机科学硕士
- 1968年:计算机科学博士
- 导师:John McCarthy(人工智能先驱,图灵奖得主)
- 论文:程序验证
历史意义:
- 美国第一批女性计算机科学博士之一(1968年)
- 当时全美计算机博士中女性不到1%
MIT生涯(1972-至今)
加入MIT(1972):
- 助理教授
- 立即开始CLU项目
CLU时期(1974-1980):
- 领导团队设计、实现CLU
- 发表大量论文
- ADT概念逐渐被接受
Argus时期(1980-1990):
- 转向分布式系统
- 设计Argus语言
- 解决实际问题(银行、航空订票)
后期研究(1990-2010):
- 拜占庭容错:如何在部分节点恶意时保持一致性
- Theta语言:继续探索抽象
- 持久化编程:数据在程序重启后保留
教学:
- MIT软件工程课程主讲人
- 培养大批PhD
- 影响数千学生
荣誉:
- 图灵奖(2008)
- 美国国家工程院院士
- 美国艺术与科学院院士
- IEEE John von Neumann Medal
至今:
- 仍活跃于学术(85岁!)
- MIT名誉教授
- 偶尔参加学术会议
性格与风格
严谨:
- 对理论精确性要求极高
- 论文逻辑严密
实践导向:
- 不满足于纯理论
- 必须构建真实系统验证
导师:
- 学生回忆她要求高但公平
- 鼓励独立思考
- 培养了很多成功的研究者
低调:
- 不追求媒体曝光
- 专注研究和教学
- 成果说话
女性榜样
打破障碍:
- 1960年代:计算机几乎全是男性
- 普林斯顿拒绝:因为性别
- 坚持:转向其他学校,最终成功
成就:
- 第二位女性图灵奖得主(第一位是Frances Allen)
- 证明女性在计算机科学同样卓越
对女性的影响:
- 很多女性研究者受她激励
- MIT女性学生的榜样
- 推动计算机科学性别平等
她的观点:
“性别不应该是障碍。专注你的工作,用成果证明自己。”
💭 为什么她值得纪念?
1. 她让软件开发变得系统化
之前:
- 代码混乱,难以理解、维护
- “意大利面代码”
之后:
- ADT提供了组织代码的方法
- 模块化、封装成为标准实践
影响:
- 软件工程成为一门工程学科
- 大型软件项目变得可管理
2. 她定义了正确的继承
LSP之前:
- 继承滥用,bug频发
- Square继承Rectangle类的问题到处都是
LSP之后:
- 明确什么是正确的子类型
- 设计原则清晰
- OOP更可靠
3. 她让分布式编程成为可能
Argus之前:
- 分布式系统难写、易错
- 部分失败、一致性问题困扰开发者
Argus之后:
- 原子性、事务简化编程
- 嵌套事务提高灵活性
- 分布式垃圾回收自动管理资源
今天的云计算、微服务:
- 都建立在她开创的概念之上
4. 她证明了理论与实践可以结合
Liskov的模式:
- 理论→实现→验证→改进
- 不是象牙塔,也不是只写代码
启示:
- 深刻的理论可以解决实际问题
- 实践反过来推动理论发展
- 两者结合才有持久影响
🔍 技术深度:ADT的形式化
代数规范(Algebraic Specification)
ADT的数学定义:
签名(Signature):
sort: Stack[T]
operations:
create: → Stack[T]
push: Stack[T] × T → Stack[T]
pop: Stack[T] → Stack[T]
top: Stack[T] → T
empty: Stack[T] → Boolean
公理(Axioms):
pop(push(s, x)) = s
top(push(s, x)) = x
empty(create()) = true
empty(push(s, x)) = false
意义:
- 完全定义了Stack的行为
- 不涉及实现细节
- 可以用于验证实现正确性
表示不变式(Representation Invariant)
例子:Stack的不变式:
I(s) ≡ 0 ≤ s.top ≤ size(s.items)
抽象函数(Abstraction Function):
AF: rep → abstract
AF(s) = <s.items[1], s.items[2], ..., s.items[s.top]>
正确性:
- 每个操作必须保持I(s)
- AF(实现后) = 规范
LSP的形式化
契约:
{ P } m() { Q }
- P:前置条件(precondition)
- Q:后置条件(postcondition)
替换原则: 如果S <: T(S是T的子类型),则:
P_S → P_T (前置条件:子更弱)
Q_T → Q_S (后置条件:子更强)
例子:
{ x > 0 } T.m(x) { result > 0 } # 父类型
{ x > 5 } S.m(x) { result > 10 } # 子类型
检查:
x > 5 → x > 0✓(前置条件更弱)result > 10 → result > 0✓(后置条件更强)- 满足LSP
📚 延伸阅读
书籍
Liskov的著作:
- 《Abstraction and Specification in Program Development》(1986)
- ADT的权威著作
- 《Program Development in Java》(2001)
- 教学用书,系统讲解设计原则
相关书籍:
- 《Object-Oriented Software Construction》(Bertrand Meyer)
- 契约式设计
- 《Design Patterns》(GoF)
- 设计模式,LSP是基础
论文
经典论文:
- “A Design Methodology for Reliable Software Systems” (1972)
- 早期软件工程思想
- “CLU Reference Manual” (1979)
- CLU语言定义
- “A Behavioral Notion of Subtyping” (1987)
- LSP的正式定义
- “Distributed Programming in Argus” (1988)
- Argus语言
在线资源
- Liskov的MIT主页:个人简介和论文列表
- CLU资料:可以在线阅读CLU手册
- ACM图灵奖演讲:Liskov的获奖演讲视频
🌟 精神遗产
”封装是基础”
Liskov的核心理念:
- 隐藏实现细节
- 暴露抽象接口
- 模块化是可维护性的关键
影响:
- 所有现代语言都支持封装
- API设计的黄金准则
”契约高于实现”
LSP的哲学:
- 子类型必须遵守父类型的契约
- 行为兼容性比名称重要
- 设计时考虑替换性
影响:
- 契约式设计(Design by Contract)
- 接口设计的指导原则
”理论指导实践”
Liskov的工作模式:
- 不满足于空谈理论
- 构建系统验证理论
- 理论和实践相互促进
启示:
- 最好的理论来自实践
- 最好的实践基于理论
- 两者不应分离
总结语:Barbara Liskov用一生证明了:好的抽象能驾驭复杂性。她创造的数据抽象、子类型理论、分布式编程概念,不仅改变了编程语言的设计,更改变了我们思考软件的方式。
从1972年的ADT到1987年的LSP,从CLU的封装到Argus的分布式事务,Liskov不仅提出理论,更将理论转化为工程实践。今天,当你写一个Java类、定义一个Python模块、设计一个微服务,你都在使用她创造的概念。
她是第二位女性图灵奖得主,但她希望被记住的不是性别,而是贡献。从被普林斯顿拒绝到MIT终身教授,从早期的性别歧视到今天的学术偶像,Liskov用成果说话,用智慧开路。她证明了:伟大不分性别,只看思想的深度和影响的广度。
最后更新: 2024年12月 本文为图灵奖系列文章,旨在以通俗方式介绍计算机科学先驱的贡献
DISCUSSION
评论与补充