第三章--第三节:抽象数据类型(ADT)

第三章:抽象数据类型(ADT)和面向对象编程(OOP)

第三节:抽象数据类型(ADT)

问题一:抽象的和用户定义的数据类型

  •  除了编程语言所提供的基本数据类型和对象数据类型,程序员可定义自己的数据类型
  • 抽象类型:强调“作用于数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。
  • 传统的类型定义:关注数据的具体表示

***一个抽象的数据类型是由它的操作定义的:(抽象数据类型的实现如下图:↓)

第三章--第三节:抽象数据类型(ADT)_第1张图片

问题二:数据类型的分类和操作的分类

    1.数据类型的分类:

        分为:可变和不可变的数据类型

  • 可变类型的变量:提供了可改变其内部数据的值的操作
  • 不变数据类型:其操作不改变内部值,而是构造新的对象

    2.抽象数据类型的操作的分类

        分为:构造器(Creators)、生产器(Producers)、观察器(Observers)、变值器(Mutators)

  • 用数学表达式来解释(如下图):↓

第三章--第三节:抽象数据类型(ADT)_第2张图片

  • 用实例解释各种操作:↓

构造器(creator):create new objects of the type。可能实现为构造函数或静态函数(如:ArrayList()、String.valueOf(Object Obj))(工厂方法)

变值器(mutator):change objects。(变值器通常返回void)返回值若为void,则必然意味着它改变了对象的某些内部状态.(返回值也可以不是void,例如Set.add()返回值就是布尔类型)

观察器(observer):take objects of the abstract type and return objects of a different type。观察抽象数据类型内部的属性

生产器(producer):create new objects from old objects of the type。从老的对象类型中创造出新的对象

***例:

    Integer.valueOf()                                  ---Creator

    BigInteger.mod()                                  ---Producer

    List.addAll()                                          ---Mutator

    String.toUpperCase()                           ---Producer

    Set.contains()                                       ---Observer

    Collections.unmodifiableList()             ---Producer

    BufferedReader.readLine()                   ---Mutator

问题五:表示独立性(Representation Independence)

    含义:client(客户端)使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端(除非ADT的操作指明了具体的前置条件和后置条件,否则不能改变ADT的内部表示)

问题六:测试一个抽象数据类型

  1. 测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
  2. 测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。

问题七:不变量(Invariants)

  • 不变量在任何时候总是true
  • 由ADT来负责其不变量,与client的任何行为无关
  • 使用不变量可以保持程序的“正确性”,容易发现错误

***不变性是不变量的一种类型(Immutability as a type of Invariants)

  • 若使用mutable的数据类型,很可能导致表示泄露(representation exposure)。并且表示泄露不仅影响不变性,也影响了表示独立性(即无法在不影响客户端的情况下改变其内部表示)
  • 所以尽量make it immutable。(关键字private相对于public可以使得类中的属性和方法仅仅对类的内部可视,在外面则无法访问;关键字final可以使得不可变的数据类型不会被再分配)

**例:下面则段代码会造成什么影响

    第三章--第三节:抽象数据类型(ADT)_第3张图片

    解:由于Data是mutable类型的变量所以d会指向与t.getTimestamp一样的位置,当你修改d时,无形中Tweet类型的 t也被修改,这样Tweet的不变性就被破坏,尽管我们已经声明了timestamp为final。用snapshot图表示如下:

    第三章--第三节:抽象数据类型(ADT)_第4张图片

        *解决方式:防御性的复制(Defensive copying):

    定义一个获取时间的新的方法,在方法中国,不是直接获取tweet类中的timestamp,而是新建一个对象将其tweet中的timesamp的值复制过去。代码如下:

    

    ***例:解释如下代码会发生什么

    第三章--第三节:抽象数据类型(ADT)_第5张图片

    解:这段代码中由于Date是可变的数据类型,而在进入循环的时候仅仅创建过一个对象date,所以当进入循环时,每次修改date,并将其赋给一个新的对象Tweet加入list时,都会修改以前加入list的Tweet中的date,用snapshot图表示如下:

        第三章--第三节:抽象数据类型(ADT)_第6张图片

    解决方式:在每次构造一个新的Tweet对象时,构造函数中的timstamp都新创建一个Data类型的对象来接收传送过来的date的timestamp的值,这样就不会在修改date时,同时修改掉以前的Tweet中的timestamp了,代码如下:

    


  • (*注意:当赋值代价很高时,不得以不能这么做;但是由此引发的潜在的bug也将很多!)
  • (除非迫不得已,否则不要把希望寄托于客户端上,ADT有责任保证自己invariants,并避免表示泄露;做好的办法就是用immutable的类型,彻底避免表示泄露)

***综上:保持不变性和避免表示泄露,是ADT最重要的一个Invariant

问题八:Rep Invariant (RI) and Abstraction Function (AF)

  •  R和A含义:  

          R:表示值构成的空间,也称为表示空间。表示空间的值是实现实际对象的值

          A:抽象值构成的空间,也成为抽象空间。抽象空间的值是客户端看到和使用的值

(ADT实现者关注表示空间R,用户关注抽象空间A)

  • R与A之间的映射关系
  1. R-->A是满射
  2. R-->A未必是单射
  3. R-->A未必是双射
  • AF与RI的含义:

            AF:抽象函数,是R和A之间映射关系的函数(AF:R->A)

             RI:表示不变性RI,某个具体的“表示”是否是“合法的”(RI:R->boolean)(也可以将RI看作所有表示值的一个子集,包含了所有合法的表示值)(也可将RI看作一个条件,描述了什么是“合法的”的表示值)

  • 不同的内部表示,需要设计不同的AF和RI

*例1

    第三章--第三节:抽象数据类型(ADT)_第7张图片

*例2

    第三章--第三节:抽象数据类型(ADT)_第8张图片

  • 用checkRep() 函数随时检查RI是否满足







你可能感兴趣的:(第三章--第三节:抽象数据类型(ADT))