构造数据抽象
《计算机程序的构造和解释》
数据抽象可以提升我们在设计程序时所位于的概念层次,提高设计的模块性,增强语言的表达能力。数据抽象相关的方法学是指:在程序中处理数据对象的表示的部分,与处理数据对象的使用的部分互相隔离。这也体现了解耦的思想。解耦思想在程序设计方面具有一般性和普适性。
在构造数据抽象这章中,表达了数据抽象的基本思想:设法构造出一些使用复合数据对象的程序,使它们就像在“抽象数据”上操作一样。基于这样的思想,作者提出了选择函数和构造函数的概念。构造函数用来将简单数据复合成抽象数据,选择函数用来对复合数据进行拆解。
在进行构造函数和选择函数实现的阶段,作者提出了一种按愿望思维的编程方法。按愿望思维顾名思义就是按照我们的想法来实现程序。例如我们要实现一个程序:将文件中的“demo”字符串替换为“Demo”。从按愿望思维的方法来说,我们可以这样实现程序:
- 读取文件
- 逐行扫描
- 匹配并替换 demo
function readFile() {}
function scanLine() {}
function replace() {}
通过按愿望思维,可以很自然的将程序进行模块化。
在对数据进行抽象后,很自然的产生了抽象屏障。因为不同的数据抽象伴随着一组相对应地操作。
理解了数据抽象的基本思想后,还需要思考数据意味着什么。这是一个深刻的问题。说数据是“由给定的构造函数和选择函数所实现的东西”显然不够。因为并不是任意的构造函数和选择函数都适合作为某一抽象数据的实现的基础。构造函数和选择函数要满足一组特定的条件,才能作为某一抽象数据的实现的基础。
由构造函数、选择函数表示的数据带来了一种设计风格:消息传递(流)。
除了可以表达简单的数据外,我们还需要构建具有层次结构的数据。具有层次结构的数据一般带有闭包的性质。闭包性质是指:通过某一个过程组合起数据对象的到的结果本身还可以通过同样的过程再进行组合。
在这章中主要介绍的数据处理的技巧:使用约定的界面、抽象数据的多重表示、数据导向的程序设计、设计通用型操作的系统。
列表作为通用的界面: 要在处理数据上抓住一些程序模式,则对我们操控数据结构的方式有着深刻的依赖性。这里要举出一个简单的例子。
界面指的是接口,可以理解为 interface,不同程序之间通过都定义了对列表的支持,在列表之上可以构建一个通用的计算框架,例如说:map、filter、reduce 等等。
抽象数据的多重表示:
抽象数据是指一个数据类型的抽象概念,多重表示则指同一个抽象数据可以用不同的数据结构来表示。AI 举例则是给出了时间的例子,时间分为 12小时表示和 24小时表示的,12小时表示的可以通过三元组表示 (上午,3,20)
,而 24 小时表示的可以用二元祖 (3, 20)
数据导向的程序设计: 为什么需要数据导向的程序设计?在我们可以识别出抽象数据的类型后,可以根据类型去调用某个适当的过程。但这种方式有一些缺陷,通用类型必须知道新增加的类型,不同人员设计的类型可能存在重名。数据导向的程序设计主要解决维护和扩展问题。 数据导向的程序设计是一种使程序直接利用 抽象数据类型-过程 对照表工作的程序设计技术。我们可以发现,这样对于大型程序,就有了集中维护的一张表,谁都可以查看表中的内容,有没有重名的过程或函数一目了然。
抽象数据类型-过程 对照表的实现依赖于抽象数据的多重表示和带标示的抽象数据类型。
设计通用型操作的系统:
通用型操作的系统是在 数据类型-过程 对照表基础上的进一步抽象。这层抽象,对于开发者来说,提供了统一的操作界面,而无需关注所使用的数据的类型。
对于编程语言本身来说,比如 JavaScript 提供了基础数据类型,boolean、number、string 等,这些数据类型可以混合起来参与四则运算。
true + 1
false + '1'
1 + '1'
对于上面的代码来说,通用型操作则是 +
,程序员则不需要关心数据的类型也能处理。当然实际中,为了准确知道运算结果,还需要关心类型是如何转换的。类型的转换,则正式通用型操作的系统所需要处理的。针对不同的类型制定不同的转换规则。
通用型操作的系统还会涉及到强类型语言的泛型处理。例如图中的 add、sub、mul、div 操作则需要支持下面的有理数、复数。在实现的时候,对于强类型语言的实现需要依赖泛型。
// todo 关于泛型可以结合 《程序设计原本》来阐述一下。