ADT: Abstract Data Types
在上学期的数据结构中我们已经接触过ADT——抽象数据型,抽象数据型是一个数学模型和在该模型上定义的操作的集合。在软件构造中ADT概念也类似,不过多了些值得注意的点,首先看一下MIT官网列出的几个点:
抽象性(abstraction):省略或隐藏低层次细节以获得更简约更高层次的idea。(Omitting or hiding low-level details with a simpler, higher-level idea.)
模块化(Modularity):将一个系统划分为组件或模块,每个组件或模块都可以与系统的其他部分分开设计、实现、测试、推理和重复使用。(Dividing a system into components or modules, each of which can be designed, implemented, tested, reasoned about, and reused separately from the rest of the system.)
封装(Encapsulation):在模块周围筑起一道“墙”,使模块对自己的内部行为负责,系统其他部分的错误不能破坏其完整性。(Building a wall around a module so that the module is responsible for its own internal behavior, and bugs in other parts of the system can’t damage its integrity.)
信息隐藏(Information hiding):将一个模块的实现细节从系统的其他部分隐藏起来,这样以后可以在不改变系统其他部分的情况下改变这些细节。(Hiding details of a module’s implementation from the rest of the system, so that those details can be changed later without changing the rest of the system.)
Separation of concerns(不知道怎么翻译,可能是各司其职吧):使一个功能成为单一模块的责任,而不是将其分散到多个模块。(Making a feature (or “concern”) the responsibility of a single module, rather than spreading it across multiple modules.)
在我看来,ADT就是模板,比接口更抽象的东西,与具体实现无关,这也是ADT最重要的特性。
The operations of ADT
ADT的四种操作为:构造器、生产器、观察器、变值器,数学化的描述如下:
然后老师上课提了一嘴,生产器和构造器有时候一样,主要是发生在实际作用于返回值的类型没有在参数体现。然后mutator这个操作,只有mutable类有,immutable是没有的,这点对我们后面的学习会产生很大的影响,实际上Java提供的对应的mutable和immutable类也是通过控制mutator实现。
An abstract type is defined by its operations
这里的基本思想是,一个ADT是由它的操作来定义的。一个类型T的操作集,以及它们的规格,完全表征了我们对T的含义。
扩展我们对spec firewall的比喻,你可以把抽象类型的值想象成硬壳,不仅隐藏单个函数的实现,而且隐藏一组相关的函数(该类型的操作)和它们共享的数据(存储在该类型值中的私有字段)。
该类型的操作构成了它的抽象性。这是public的,对使用该类型的客户可见。
实现该类型的类的字段,以及帮助实现复杂数据结构的相关类,构成了一个特定的表示。这一部分是private的,只对类型的实现者可见。
表示独立性(Rep Independence):
关键的是,一个好的ADT应该是表示独立的。这意味着ADT使用与它的表示方式(用于实现它的数据结构)无关,因此表示方式的变化对抽象类型本身以外的代码没有影响。例如,数组提供的操作与数组在内部是否被表示为一个连续的内存块、一个链表或一个哈希表无关。
作为一个实现者,只有当ADT的操作被完整地指定为先决条件(pre-conditions)和后决条件(post-conditions)时,你才能安全地改变ADT的表示,这样客户就知道要依赖什么,你也知道你可以安全地改变什么。
抽象函数(Abstract Function)与表示不变量(Rep Invariant):
抽象函数将R空间的值与A空间的值对应起来,并且必须是满射,必须满足客户看到的所有值都有对应。AF并不单单取决于R与A。
表示不变量:RI:R->boolean,RI的定义为:RI(r)=true 当且仅当 r被AF匹配。因此,一方面RI可以视作是R的一个子集;另一方面,AF的映射我更愿意将他描述为RI->A,因为只有RI里的值才能被匹配,更符合数学上函数的定义。我们可以编写checkRep函数来检查RI是否满足。
有益变值(Beneficent mutation):
对于immutable来说,一旦某个对象满足RI,就不会再有违背的可能性,因为没有mutator改变属性;对于mutable来说,mutator存在潜在改变RI的可能性,如果一个mutator从不改变一个R空间所对应的A空间值,则称为Beneficent mutation。
Safety from exposure:
书写这部分时,要从fields和method两方面来考虑。比如fields是private and final或者immutable,method返回immutable或者对mutable fields进行defensive copy。
再看规约:
返回来再看ADT的规约,如下图所示,事实上我们ADT规约指的就是图中clients所能见的那一部分:这包括参数、返回值和由其操作抛出的异常。每当spec需要引用ADT的值时,它应该将该值描述为抽象值,也就是抽象空间A中的数学值。
spec不应该谈论rep的细节,或者代表空间R的元素。它应该认为rep本身(私有字段)对客户不可见,就像方法体和它们的局部变量被认为是不可见的。这就是为什么我们把rep的不变性和抽象函数写成类的主体中的普通注释。
ADT也可以隐藏pre-conditions,因为依靠客户并不严格可靠。将pre-conditions隐喻于参数名中,或直接创建对应RI的ADT。
How to establish invariants
为了保持不变量我们需要:初始创建对象时就让RI正确并且操作并不会改变RI。这意味着:constructors和producers必须为新的对象实例建立RI;以及mutators、observers和producers必须为现有的对象实例保留该RI。
规则如下:
如果一个ADT的RI被
1、由constructors和producers建立。
2、被mutators、observers和producers所保留
3、没有发生表示暴露。
那么该不变性对该抽象数据类型的所有实例RI都是true的。
等价性:
这部分还蛮简单的,对immutable来说,有AF等价性,观察等价性;mutable来说有AF等价性、观察等价性、行为等价性。
Mutable的值得关注一下,行为等价性就是引用直接相等,与Objects的equals对应。不过mutable倾向于实现观察等价性。
也可以自己重写equals以实现不同需求,但是注意一定同时重写equals和hashCode!
差不多就这些吧,本身也是一边复习可能也有理解不准确的地方,恳请指出!