软件构造博客4----ADT和OOP

目录

六.抽象数据类型 (ADT)

1 Abstraction and User-Defined Types

2 Classifying Types and Operations

3 Abstract Data Type Examples

4 Designing an Abstract Type

5 Representation Independence

6 Testing an Abstract Data Type

7 Invariants

 8 Rep Invariant and Abstraction Function

9 Beneficent mutation

10 Documenting the AF, RI, and Safety from Rep Exposure

11 ADT invariants replace preconditions


六.抽象数据类型 (ADT)

抽象数据类型与表示独立性:能够分离程序中数据结构的形式和对其使用的方式。如何设计良好的抽象数据结构,通过封装来避免客户端获取数据的内部表示(即“表示泄露”),避免潜在的bug——在client和implementer之间建立“防火墙”,可以很好地防止表示泄露。

abstraction functions:抽象函数(AF);representation invariants:表示不变量(RI)

--通过抽象函数和表示不变量的概念,对类实现ADT意味着什么这一更重要的数学概念。这些数学概念在软件设计中非常实用。rep不变量更容易捕获有损坏的数据结构引起的错误。

1 Abstraction and User-Defined Types

除了编程语言所提供的基本数据类型和对象数据类型,程序员可定义自己的数据类型。数据抽象就是指由一组操作所刻画的数据类型。例如:数字可以进行加或者乘运算;字符串可以进行连接或者取出其中一部分。相反地,传统的类型定义只关注数据的具体表示。抽象类型强调“作用于数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。

数据类型的操作和规约刻画了T的特征。ADT由操作定义,与其内部如何实现无关。

2 Classifying Types and Operations

可变类型的对象提供了可改变其内部数据的值的操作,而不可变数据类型的操作不可改变内部值,而是构造新的对象。有些类型既提供可变类型也提供不可变类型。

Creators:构造器:从无到有 Producers:生产器:从有到新

Observers:观察器 Mutators:变值器,改变对象属性的方法

构造器可能实现为构造函数或静态函数,实现为静态方法的构造器通常称为工厂方法。

变值器通常返回 void:如果返回值为void,则必然意味着它改变了对象的某些内部状态;变值器也可能返回非空类型。

3 Abstract Data Type Examples

 List是可变类型,同时也是接口。

例子:

Integer.valueof():构造器   BigInteger.mod():生产器   List.addAll()   变值器

String.toUpperCase():   生产器    Set.contains():   观察器   Map.keySet()   观察器

Collections.unmodifiableList():   生产器   BufferedReader.readLine()   变值器

4 Designing an Abstract Type

良好ADT的设计:靠“经验法则”,提供一组操作,设计其行为规约 spec。

规则1:设计简洁、一致的操作。设计一组简单操作,通过简单操作的组合实现复杂的操作。操作的行为应该是内聚的。

规则2:要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低。判断方法:对象每个需要被访问到的属性是否都能够被访问到。例如,因为用遍历方式获取list的size太复杂了,所以我们提供size()操作,方便client使用。

规则3:要么抽象、要么具体,不要混合 --- 要么针对抽象设计,要么针对具体应用的设计。面向具体应用的类型不应包含通用方法,面向通用的类型不应包含面向具体应用的方法。

5 Representation Independence

表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。通过前提条件和后置条件充分刻画 了ADT的操作,spec规定了client和implementer之间的契约,明确了client知道可以依赖哪些内容,implementer知道可以安全更改的内容。

 

6 Testing an Abstract Data Type

测试creators producers and mutators:调用observers来观察这些operations的结果是否满足spec;

测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。

风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。

7 Invariants

ADT需要始终保持其不变量。不变量就是程序在任何时候总是true的性质。例如:immutability就是一个典型的不变量。由ADT来负责其不变量,与client端的任何行为无关。

为什么需要不变量:保持程序的“正确性”,容易发现错误。如果没有这个不变量, 那么在所有使用String的地方,都要检查其是否改变了。因此,我们在编程的过程中总是要假设client 有“恶意”破坏ADT不变量的行为,这就是所谓的防御式编程。

表示泄露不仅影响不变量,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示。

private修饰符表明变量只能在class里面访问;public可以在class外访问。

final变量表明变量一旦赋值,不能够再修改。当复制代价很高时不得不这么做,但是由此引发的bug也将会有很多。除非迫不得已,否则不要把希望寄托于客户端上,ADT有责任保证自己的invariants,并避免“表示泄露”。最好的办法就是使 用immutable的类型,彻底避免表示泄露。

 8 Rep Invariant and Abstraction Function

表示值构成的空间:实现者看到和使用的值。一般情况下ADT的表示比较简单,有些时候需要复杂表示。

抽象值构成的空间:client看到和使用的值。ADT开发者关注表示空间R,client关注抽象空间A。

每一个A中元素都由R中元素对应 :满射

A中一些元素被数个R中元素对应:未必单射

R中不是所有元素都有映射:未必双射

抽象函数:R和A之间映射关系的函数,即如何将R中的每一个值解释为A中的每一个值。表示不变性RI指某个具体的“表示”是否是“合法的”。也可将RI看作所有表示值的一个子集,包含了所有合法的表示值 或者看做一个条件,描述了什么是“合法”的表示值。

同一个ADT,可以有多种表示;不同的内部表示,需要设计不同的AF和RI。选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射 到抽象空间中的值。同样的表示空间R,可以有不同的RI。即使是同样的R、同样的RI,也 可能有不同的AF,即“解释不同”。

设计ADT:(1) 选择R和A;(2) RI --- 合法的表示值; (3) 如何解释合法的表示值 ---映射AF做出具体的解释:每个rep value如何映射到abstract value。而且要把这种选择和解释明确写到代码当中

编写代码的过程中要时刻检查RI是否满足。在所有可能改变rep的方法内都要检查;Observer方法可以不用, 但建议也要检查,以防止你的“万一”。

9 Beneficent mutation

对immutable的ADT来说,它在A空间的abstract value应是不变的,但其内部表示的R空间中的取值则可以是变化的。

10 Documenting the AF, RI, and Safety from Rep Exposure

我们可以在在代码中用注释形式记录AF和RI。要精确的记录RI:rep中的所有fields何为有效;精确记录AF:如何解释每一个R值。ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等。如果规约里需要提及“值”,只能使用A空间中的“值”。ADT的规约里也不应谈及任何内部表示 的细节,以及R空间中的任何值。ADT的内部表示(私有属性)对外部都应严格不可见。故在代码中以注释的形式写出AF和RI而不 能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏。

构造器和生产器在创建对象时要确保不变量为true;变值器和观察器在执 行时必须保持不变性。在每个 方法return之前,用checkRep()检查不变量是否得以保持。

表示泄漏的风险:一旦泄露,ADT内部表示可能会在程序的任何位置 发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是 否能够始终保持为true。

11 ADT invariants replace preconditions

用ADT不变量取代复杂的 Precondition,相当于将复杂的precondition封装到了ADT内部。

七.面向对象的编程

将抽象数据类型的接口与其实现分离:使用java接口强制实现这种分离。

使用接口定义ADT,并编写实现接口的类。

1 Basic concepts: object, class, attribute, and method

Object:状态 行为----一组状态和行为的复合体

Class:类,定义方法和作用域  生成实例:实例化,创建对象

静态方法无法直接调用非静态成员;非静态方法要先生成实例再调用方法 静态方法存储在栈上

2 Interface and Enumerations

接口只有方法的定义而没有实现;接口可以继承和拓展;一个类可以实现多个接口,从而具备了多个接口的方法;同时一个借口可以有多种实现类。

接口确定ADT规约,类实现ADT。也可以不需要接口直接使用类作为ADT,既有ADT定义悠悠ADT实现。在实际开发中更倾向于使用接口来定义变量。

接口中的每个方法在所有类中都要实现,缺点是会导致部分方法的重复实现。通过default方法,在接口中统一实现某些功能,无需在各个类中重复实现它。default 方法的典 型使用方式:以增量式的为接口增加额外的功能而不破坏已实现的类。

4 Encapsulation and information hiding(封装和信息隐藏)

信息隐藏的优点:解耦组成系统的类,允许单独开发,测试优化,使用,理解并修改它们,允许并行开发,维护压力小,实现有效性能调整单独优化提高软件重用,松散耦合的类可以在其他环境中使用。

使用接口类型声明变量,客户端仅使用接口中定义的方法,客户端代码无法直接访问属性,但仍然可以访问其他的非接口成员。

private:只能在声明的类中使用  prtected:生成类的子类和包内都可以使用

public:在哪里都可以使用

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值