软件构造月总结2
续接上文:https://blog.csdn.net/qq_42991073/article/details/105221557
1.数据类型和类型检查
基本数据类型:int,long,byte,short,char,float,double,boolean
只有值,没有ID;Immutable;在栈中分配内存;代价低
对象数据类型:Class,interface,arrays,enums,annotations
既有ID也有值;有的是Immutable,有的是mutable;在堆中分配内存;代价高
静态类型检查:没有运行代码(在编译阶段),然后系统对代码的检查
动态类型检查:运行代码时进行的代码检查
静态类型检查>动态类型检查>无检查
静态类型语言:在编译阶段进行类型检查
动态类型语言:在运行阶段进行类型检查
静态类型检查(关于类型的检查):语法错误;类名、函数名错误;参数数目错误;返回值类型错误
动态类型检查(关于值的检查):非法的参数值;非法的返回值;越界;空指针
Mutable(创建后,值是可以被改变的)
Immutable(一旦被创建,值是不能改变的)
老师给的例子:
String t = s;
t = t + 'c';
StringBuilder tb = sb;
tb.append("c");
我们由这个snapshot图就能很清楚的看到,String是一个Immutable的类型,而StringBuilder是一个mutable的类型(这两个类型在多个引用同时指向的时候回有很大差别)
Immutable的安全,但是mutable的性能好
2.snapshot
基本类型的值
对象类型的值
不可变对象:用双线的椭圆
不可变得引用(final):
3.Complex data types
Array:这个跟C语言等其它语言的应用方式差不多(直接举出老师给的例子)
int[] a = new int[100];
int i = 0;
int n = 3;
while (n != 1) {
a[i] = n;
i++;
if (n % 2 == 0) {
n = n / 2;
}
else {
n = 3 * n + 1;
}
}
a[i] = n;
i++;
List:这个就是一个可变长度的数组(是一个接口)
有add,set,size,get等方法
list.get(2);
list.set(2, 0);
list.size();
Set:集合类(是一个接口)
s1.contains(e) //test if the set contains an element
s1.containsAll(s2)//test whether s1 ⊇ s2
s1.removeAll(s2) //remove s2 from s1
Map:储存键值对的一个数组(是一个接口)
map.put(key, val) //add the mapping key → val
map.get(key) //get the value for a key
map.containsKey(key) //test whether the map has a key
map.remove(key) //delete a mapping
Iterator:迭代器(删除某元素的时候非常有用)其实跟循环没啥差别
Iterator iter = lst.iterator();
while(iter.hasNext()){
String str = iter.next();
System.out.println(str);
}
4.抽象数据类型(ADT)
抽象数据类型就是指,我们使用一些数据,但是我们不能直接使用这些值,我们只能调用一些的方法来间接地使用这些数据
可变类型的对象:提供了可改变其内部数据的值的操作
不变数据类型:其操作不改变内部值,而是构建新的对象
Creator(构造器):t*->T
Producer(生产器):T+,t->T
Observer(观察器):T+,t*->t
Mutator(改变器):T+,t*->void/t/T
老师在课件中举出过几个例子:
Integer.valueOf() //Creator
BigInteger.mod() //Producer
List.addAll() //Mutator
String.toUpperCase() //Producer
Set.contains() //Observer
Map.keySet() //Observer
Collections.unmodifiableList() //Producer
BufferedReader.readLine() //Mutator
设计ADT的时候:
设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约 spec
设计简洁、一致的操作
要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低
表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。除非ADT的操作指明了具体的pre- 和post-condition,否则不能改变ADT的内部表示——spec规定了client和implementer之间的契约。
测试ADT:
测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。
不变量(在任何时候总是true,不应该改变,由ADT来负责其不变量,与client端的任何行为无关)
5.AF和RI(Rep Invariant and Abstraction Function)
ADT开发者关注表示空间R,client关注抽象空间A
抽象函数:R和A之间映射关系的函数,即如何去解释R中的每一个值为A中的每一个值。
AF : R → A 满射、非单射、未必双射
RI : R → boolean
即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”。
设计ADT:(1) 选择R和A;(2) RI — 合法的表示值;(3) 如何解释合法的表示值 —映射AF
做出具体的解释:每个rep value如何映射到abstract value
!!!随时检查RI是否满足:
在所有可能改变rep的方法内都要检查,Observer方法可以不用,但建议也要检查,以防止你的“万一”
6.Beneficent mutation
对immutable的ADT来说,它在A空间的abstract value应是不变的。
但其内部表示的R空间中的取值则可以是变化的。
这种mutation只是改变了R值,并未改变A值,对client来说是immutable的 “AF并非单射”,从一个R值变成了另一个R值 ,但这并不代表在immutable的类中就可以随意出现mutator!
7.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,在对象发生变化时,不变量也要为true
构造器和生产器在创建对象时要确保不变量为true
变值器和观察器在执行时必须保持不变性。
在每个方法return之前,用checkRep()检查不变量是否得以保持。
表示泄漏的风险:一旦泄露,ADT内部表示可能会在程序的任何位置发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是否能够始终保持为true。
用这三个标准来检查你的ADT是否保持不变量?
established by creators and producers;
preserved by mutators, and observers;
no representation exposure occurs,
上一篇: junit测试spring注入的两种方法
下一篇: C#在子线程中更新窗口部件的写法