一。可变类和不可变类(Mutability and Immutability)
可变类有Mutator方法,不可变类没有Mutator方法,该类一旦被实例化,不能再被改变
二。SnapShot Diagram(程序快照图)
用于描述程序运行时的内部状态。
用于描述程序运行时的内部状态
便于程序员之间交流
便于刻画各类变量随时间变化
便于解释思路
基本类型的值的表示
对象类型的值的表示
不可变对象:使用双线椭圆
String s= “a”; s=s+"b";
表示:
可变对象使用:单线椭圆
StringBuilder sb= new StringBuilder("a"); sb.append("b");
表示:
不可变的引用(使用final进行限制):
使用双箭头指向
三。Designing Specification(设计规约)
(1)规约和实现的区别:
规约只说要做什么,不说怎么实现
(2)行为等价性(Behavioral Equivalence)
判断2个方法是否可以相互替换需要站在客户端视角看行为等价性,即如果规约等价,则这两个方法等价
(3) 规约结构
pre-condition (前置条件) 和 post-condition(后置条件)
前置条件:对客户端的约束,在使用方法时必须满足的条件 requires
后置条件:对开发者的约束,方法结束后必须满足的条件 effects
契约:如果前置条件满足了,那么后置条件也应该满足,前置条件不满足,则方法可以做任何事
方法的静态类型说明也是一种规约(方法签名)
例如: public void change(int a)
除非在后置条件里声明过,否则不应该修改输入参数
测试用例也需要符合规约
(4)规约的强度
首先要比较的方法签名要相同,这样才可以进行比较
然后,前置条件越弱,后置条件越强,则桂规约强度越强
(5)设计规约的要求
1.内聚(coherent)
Spec描述的功能应该单一,简单,易理解
2.后置条件应该信息丰富
3.规约要足够强
太弱的 spec client 不放心、不敢用 因为没有给出足够的承诺 。
4.也不能太强
太强的 spec ,在很多特殊情况下难以达到,给开发者增加了实现的难
度( client 当然非常高兴)。
5.在规约里使用抽象类型,可以给方法的实现提与客户端更大的自由度。
6.For programmer:
是否应该使用前置条件?在方法正式执行之前,是否要检查前置条件已被满足?(要满足前置条件还是可以不需要前置条件,在方法内直接check前置条件是否满足)
归纳: 是否使用前置条件取决于 (1) check 的代价 ;(2) 方法的使用范围
如果只在类的内部使用该方法 ( private),那么可以不使用前置条件,在使用
该方法的各个位置进行 check 责任交给内部 client
如果在其他地方使用该方法 ( public),那么必须要使用前置条件,若 client 端
不满足则方法抛出异常。
ADT(Abstract Data Type)
(1)抽象类型:强调“作用于数据上的操作”,程序员和
client 无需关心数据如何具体存储的,只需设计 使用操作即可。
(2)分类:
可变和不可变数据类型(有无Mutator方法)
操作类型分类:
Creator(构造器):从无到有构造一个对象
可能为构造函数或者静态函数,也可以是工厂方法
Producaer(生产器):从有到有,根据已有对象产生一个新对象,
Observer(观察器):观察ADT的侧面属性,根据ADT返回其侧面属性
Mutator(变值器):改变对象属性的方法
变值器通常返回void,如果返回值为 void ,则必然意味着它改变了对象的某些内部状态,但是也可以返回非空类型
(3) 设计ADT
设计好的 ADT ,靠“经验法则”,提供一组操作,设计其行为规约 spec
(4)表示独立性Representation Independence
client使用ADT时无需考虑其内部如何实现,ADT内部的变化不应该影响外部spec和客户端。
(5)测试ADT
测试 creators, producers, and mutators :调用 observers 来观察这些operations 的结果是否满足 spec
测试 observers :调用 creators, producers, and mutators 等方法产生或改变对象,来看结果是否正确。
风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。
(6)Invariants of an ADT(保持不变量)
在任何时间均为true,由ADT来负责其不变量,与client端的任何行为无关
为什么需要不变量:保持程序的“正确性”,容易发现错误
(7)表示泄露(Rep exposure)
把ADT的内部引用返回到了cilent,可以让其随意修改,不仅影响不变性,也影响表示独立性,无法在不影响客户端的情况下改变其内部表示
防止Rep exposure:
1.私有属性(private)
2.final限制 private final int a =4;
3.返回对象引用的复制到客户端
4.最好的办法就是使用 immutable 的类型,彻底避免表示泄露
保持不变性和避免表示泄露,是ADT最重要的一个Invariant
(8)AF抽象函数和RI表示不变量
AF是一个从R空间(表示空间)映射到A空间(抽象空间)的函数
抽象值构成的空间是client看到和使用的值
表示空间是实际的值
用表示空间内的一个值表示抽象空间内的一个值
ADT开发者关注表示空间R,client关注抽象空间A
AF性质:满射,未必单射,未必双射
性质的意思就是在R空间内不是所有值都可以对应到A空间中,但是A空间中的值必须都被对应。
RI表示不变量
表示不变性 RI :某个具体的“表示”是否是“合法的
也可将 RI 看作:所有表示值的一个子集,包含了所有合法的表示值
也可将 RI 看作:一个条件,描述了什么是“合法”的表示值
RI从R空间中筛选出了一部分合法值
设计ADT:
(1)选择R和A
(2)RI----合法的表示值
(3)如何解释合法的表示值---映射AF
做出具体的结束:每个rep value 如何映射到abstract value
需要随时检查RI是否满足CheckRep();
在所以可能改变rep的方法内都要检查
(9)有益的改变
对 immutable 的 ADT 来说,它在 A 空间的 abstract value 应是不变的。
但其内部表示的 R 空间中的取值则可以是变化的。
不可变类可以出现有益的改变,并不影响其不可变的属性,但是不要随意出现
(10)注释AF和RI(AF和RI是给程序员看的,所以可以涉及R空间,并且不能展示给client看)
//AF //说明AF //RI //说明RI //Rep exposure //给出理由,说明没有表示泄露
(11)ADT规约要求
ADT的规约里只能使用client可见的内容来撰写,包括参数,返回值,异常等
ADT的规约内不应该涉及任何内部表示,ADT的内部表示(私有属性)对外部应该严格不可见
(12)确保Invariants为true
在对象初始状态不变量为true时,在对象变化时,不变量也应该为true
-
- Creator和producer在创建对象是保证不变量为ture
- Mutator和observer在观察和变值时保证不变量为ture
- 在每个方法return时,用checkRep()检查不变量是否得以表示
- 没有表示泄露