3.4 可扩展性问题
面向对象程序设计通常被认为是软件可扩展性方面的灵丹妙药。但是,“可扩展”究竟意味着什么呢?
可扩展性问题说的是如何定义数据类型(结构+操作),使之能够支持两种形式的扩展:添加新的表示变体,或添加新的操作。
这里,ADT 的意思遵从 Cook 的用法。然而我们需要澄清,这里对扩展性问题的讨论实际上将对象与变体类型(variant type)(即代数数据类型(algebraic data types))进行对比。我们关心的是可扩展的 实现 。这里不关心界面的抽象。
事实表明,ADT 和对象分别都能很好地支持可扩展性的一个维度,但是在另一维度就不行了。让我们用一个众所周知的例子来研究此问题:简单表达式的解释器。
3.4.1 ADT
先来考虑 ADT 的做法。表达式的数据类型有三种变体:
(define-type Expr
[num (n number?)]
[bool (b boolean?)]
[add (l Expr?) (r Expr?)])
接下来定义解释器,这是一个函数,用 type-case 处理抽象语法树:
(define (interp expr)
(type-case Expr expr
[num (n) n]
[bool (b) b]
[add (l r) (+ (interp l) (interp r))]))
这是一道很好的 PLAI 练习题。举个例子:
> (define prog (add (num 1)
(add (num 2) (num 3))))
> (interp prog)
6
扩展:新的操作
先来考虑给表达式添加一个新操作。除了对表达式进行解释,我们还想做类型检查,也就是确定它将算得的值的类型(在这里,是 number
或 boolean
)。这很简单,但是能检测到解释过程中出现的失败的情况,比如对两个不是数字的东西进行相加操作:
(define (typeof expr)
(type-case Expr expr
[num (n) 'number]
[bool (b) 'boolean]
[add (l r) (if (and (equal? 'number (typeof l))
(equal? 'number (typeof r)))
'number
(error "类型错误:并非数"))]))
求一下之前那个程序的类型:
> (typeof prog)
'number
我们的类型检查器会拒绝不合理的程序:
> (typeof (add (num 1) (bool #f)))
类型错误:并非数
反思一下这个扩展案例,我们看到一切都很顺利。想要新的操作,我们只需要定义新的函数。这种扩展是模块化的,因为只需要在一个地方新加定义。
扩展:新的数据
接下来考虑另一个维度的可扩展性:添加新的数据变体。假设我们扩展这里的简单语言,增加新的表达式: ifc
。扩展后数据类型的定义是:
(define-type Expr
[num (n number?)]
[bool (b boolean?)]
[add (l Expr?) (r Expr?)]
[ifc (c Expr?) (t Expr?) (f Expr?)])
修改 Expr
的定义加上这个新变体破坏了所有现有的函数定义! interp
和 typeof
都不再成立,因为它们用 type-case
对表达式“按类型处理”,但是并没有处理 ifc
的情况。我们需要修改它们,加上对 ifc
的处理:
(define (interp expr)
(type-case Expr expr
[num (n) n]
[bool (b) b]
[add (l r) (+ (interp l) (interp r))]
[ifc (c t f)
(if (interp c)
(interp t)
(interp f))]))
(define (typeof expr)
(type-case Expr expr
[num (n) 'number]
[bool (b) 'boolean]
[add (l r) (if (and (equal? 'number (typeof l))
(equal? 'number (typeof r)))
'number
(error "类型错误:并非数"))]
[ifc (c t f)
(if (equal? 'boolean (typeof c))
(let ((type-t (typeof t))
(type-f (typeof f)))
(if (equal? type-t type-f)
type-t
(error "类型错误:两个分支的类型不同")))
(error "类型错误:并非布尔值"))]))
程序是正确的:
> (define prog (ifc (bool false)
(add (num 1)
(add (num 2) (num 3)))
(num 5)))
> (interp prog)
5
这种情况下的可扩展性就不怎么样了。我们必须修改数据类型的定义,然后修改所有的函数。
总而言之,使用 ADT,添加新的操作(如 typeof
)是模块化的所以很容易,但添加新的数据类型(例如 ifc
)则不是模块化的所以非常麻烦。
3.4.2 OOP
对象在这些场景下表现如何?
我们从面向对象版本的解释器开始:
(define (bool b)
(OBJECT () ([method interp () b])))
(define (num n)
(OBJECT () ([method interp () n])))
(define (add l r)
(OBJECT () ([method interp () (+ (-> l interp)
(-> r interp))])))
请注意,遵循面向对象的设计原则,每个表达式对象都知道如何解释自己。程序中不存在某个中央解释器能处理所有的表达式。解释程序是通过给该程序发送 interp
消息来完成:
> (define prog (add (num 1)
(add (num 2) (num 3))))
> (-> prog interp)
6
扩展:新的数据
要添加新的数据,比如条件对象 ifc,可以简单地定义新的对象工厂,其中包含该新对象处理 interp 消息的定义:
(define (ifc c t f)
(OBJECT () ([method interp ()
(if (-> c interp)
(-> t interp)
(-> f interp))])))
现在可以解释包含条件的程序了:
> (-> (ifc (bool #f)
(num 1)
(add (num 1) (num 3))) interp)
4
这表明,与 ADT 相反,使用 OOP 添加新类型的数据是直接的、模块化的:只需创建新对象即可。对比 ADT,这是明显的优势。
扩展:新的操作
但在得出结论,认为 OOP 是软件可扩展性的灵丹妙药之前,我们必须考虑另一种扩展场景:添加操作。假设我们和以前一样,需要检查程序的类型。这意味着表达式对象现在还需要理解“typeof”消息。要做到这一点,我们就必须修改所有的对象定义:
(define (bool b)
(OBJECT () ([method interp () b]
[method typeof () 'boolean])))
(define (num n)
(OBJECT () ([method interp () n]
[method typeof () 'number])))
(define (add l r)
(OBJECT () ([method interp () (+ (-> l interp)
(-> r interp))]
[method typeof ()
(if (and (equal? 'number (-> l typeof))
(equal? 'number (-> r typeof)))
'number
(error "类型错误:并非数"))])))
(define (ifc c t f)
(OBJECT () ([method interp ()
(if (-> c interp)
(-> t interp)
(-> f interp))]
[method typeof ()
(if (equal? 'boolean (-> c typeof))
(let ((type-t (-> t typeof))
(type-f (-> f typeof)))
(if (equal? type-t type-f)
type-t
(error "类型错误:两个分支的类型不同")))
(error "类型错误:并非布尔值"))])))
程序是正确的:
> (-> (ifc (bool #f) (num 1) (num 3)) typeof)
'number
> (-> (ifc (num 1) (bool #f) (num 3)) typeof)
类型错误:并非布尔值
这个可扩展性场景下,我们被迫修改所有的代码才能添加新方法。
总而言之,对对象来说,添加新的数据类型(例如 ifc)模块化所以容易,但添加新的操作(例如 typeof)不模块化所以麻烦。
请注意,这就是 ADT 的对偶情况!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论