1.2 节主要讲解数据抽象相关的东西。
-
为什么会出现数据抽象的概念?
- 如果只有原始的数据类型,那我们的程很大程度上会被限制在只做些算术运算上,无法操作字符串、图像声音和各种各样的庞大数据。
- 某种程度上说,数据抽象也是和面向对象编程的思想相互依存。要想实现数据抽象,就需要支持数据类型的自定义,面向对象编程的思想正好可以满足。或者说,正是出于对各种数据进行抽象的需要,所以有了面向对象编程的思想?
-
什么是数据抽象?
- 将数据和函数关联起来,并将数据的表示方法对使用者隐藏起来的表示方式。在使用抽象数据类型时,我们的注意力几种在 API 描述的操作上而不会去关心数据的表示。在实现抽象数据类型时,我们的注意力集中在数据本身并实现对该数据的各种操作。例如 java 中的数组、字符串等类型,就是数据抽象,我们并不关心内部是如何存储数据的,我们只是通过暴露出来的 API 操作数据。
- 对象的三大关键性质:
- 状态:即数据类型中的值
- 标识:唯一区分对象的标识符,许多时候我们都通过内存中的位置来进行区分
- 行为:操作数据的方法
-
如何实现数据抽象?
知道了什么是数据抽象之后,那如何自定义抽象数据类型呢?说人话就是如何实现自定义的类,需要以下几个主要元素:
- 实例变量:存储每个对象的状态,包括不同作用域类型的变量
- 构造函数:初始化对象的状态并创建一个对象的标识
- 实例方法:定义每个对象的行为。包括 API 与实现的定义
-
设计数据类型时需要注意的点。
- 封装。封装有许多好处,可以保持各个模块之前的独立性。
- 设计 API。这应该是现如今写代码最有挑战性的一项任务了。常见的 API 设计陷阱:
- 难以实现
- 难以使用,使用 API 甚至比不使用代码上还要复杂
- 范围太窄,缺少需要的方法
- 范围太宽,包含许多不会被使用的方法。这种缺陷可能比较常见,因为新增方法很简单,删除方法却很困难
- 太粗略,无法提供有效的抽象
- 太详细,国语细致或发散导致无法使用
- 太过于依赖某种特定的数据表示,这种缺陷也比较难以避免,因为数据表示是数据抽象类型实现的核心
- 算法与抽象数据类型
- 接口继承,即接口实现,通过接口联系两个没有交集的类
- 实现继承,即平常所说的子类。但是另一方面,子类继承也会影响模块化编程,原因有两点:
- 父类的任何改动都会影响它的所有子类
- 子类代码可以访问所有实例变量,因此可能会扭曲父类代码的意图
- 字符串表示的习惯
- 封装类型,内置的数据类型
- 等价性,如何判定两个对象相等
- 内存管理,对象为引用数据类型,如何在必要时及时释放内存
- 原始数据类型生命周期单一,出了作用域后直接释放就可以
- 引用数据类型生命周期不确定。C/C++ 的手动释放和java 的垃圾回收机制
- 不可变性
- 不可变的数据类型比可变的数据我俩看使用更容易,误用更困难,因为能够改变它们的值的方式少,调试起来也更简单。但不可变性的缺点有:一、需要为每个值创建一个新对象,二、java 中的 final 只能用来保证原始数据类型的实例变量的不可变性,但无法确保实例变量所引用的对象的值不可变
- 契约式设计,即异常和断言