
MIT6.031 软件构造 ADT&OOP
软件构造之 ADT OOP
1. 数据类型
2. 类型检查
类型检查:判断这个值在不在数据类型的取值范围中
静态类型检查在IDEA里就会爆红,主要是关于数据类型的检查
3. 值与引用的改变
改变变量 = 改变对象引用 = 改变Stack区
改变变量的值 = 改变对象object = 改变Heap区
如 int a = 20;
Person person = new Person();
3.1 Mutable / Immutable
Immutable 引用,其实就是Java种的final:
3.2 防御式拷贝
不要传递可变的参数,不要返回可变的返回值
可变类型的方法(尤其是 get()
方法)返回值需要防御式拷贝:保持原对象不变,返回一个新的具有相同数值的对象在方法中完成操作。
public final class Period {
// Date 是一个可变类型
private final Date start, end;
public Period(Date start, Date end) {
if (start.after(end)) throw new IllegalArgumentException();
// 可变类型方法要返回一个新的对象 即防御式拷贝
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
}
public Date getStart(){
// 可变类型方法要返回一个新的对象 即防御式拷贝
return new Date(start.getTime());
}
public Date getEnd(){
return new Date(end.getTime());
}
}
3.3 Snapshot diagram 快照图
表示范畴:可变对象用单椭圆,不可变对象用双椭圆。
表示对象类型:在圈中标注对象类型(注意不是变量名,变量名在外面呢!)
表示变量(可选):在圈中记录内部变量名,变量又有向外的引用
表示变量类型:在圈中的变量名前标注变量类型
表示内部的索引:在圈中用索引的数字表示
4. 规约结构 Specification
class Hailstone {
/**
* Compute a hailstone sequence.
* @param n Starting number for sequence. Assumes n > 0.
* @return hailstone sequence starting with n and ending with 1.
*/
public static List<Integer> hailstionSequance(int n){
}
}
Method signature 方法的签名:给出方法名称、参数类型、返回类型、抛出异常
前置条件:对客户端的约束,使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
static int find(int[] arr, int val)
requires: 变量 val 只能在arr数组中出现一次
effects: 返回索引 i 满足 arr[i] = val
整体结构是一个逻辑蕴含关系:前置条件满足,则后置条件必须满足;
如果在调用方法时前提条件不成立,则实现不受后置条件的约束:如 arr[] 数组出现两次val值时 方法返回0 或抛出异常 均可。
4.1 Java 中的规约: Javadoc
静态类型声明与静态类型检查
方法前的注释,需要开发者人工判断
参数被
@param
注解描述, 结果被@return
和@throw
子句描述把前置条件写在
@param
中,在@return
和@throws
注解中写后置条件
/**
* Find a value in an array
* @param arr 要查找的 arr 数组,变量 val 只能在arr数组中出现一次
* @param val 要寻找的变量 val
* @return 返回索引 i 满足 arr[i] = val
*/
static int find(int[] arr, int val)
方法规约可以论述方法的参数和返回值,但不应该讨论方法的局部变量或方法类的私有字段。实现者应该考虑 spec 读者看不到的实现部分(即方法的实现)
4.2 变异方法对于可变对象的更改
如何在规约中描述对可变对象的更改。- 在后置条件中说明
4.3 异常
Checked Exception
Unchecked Exception
5. 规约的强度
规约的强度
spec变强:更放松的前置条件 + 更严格的后置条件
越强的规约,意味着 implementer 的自由度和责任越重,而 client 的责任越轻。
规约的确定性
确定的规约:给定一个满足 precondition 的输入,其输出是唯一的、明确的
欠定的规约:同一个输入可以有多个输出
非确定的规约:同一个输入,多次执行时得到的输出可能不同
规约的陈述性
操作式规约给出了方法实现的步骤,例如:伪代码。不建议使用,如有必要可以写在方法体的注释中
声明式规约:没有内部实现的描述,只有“初-终”状态。声明式规约更有价值,通常更短,更容易理解,最重要的是,不会暴露客户端可能依赖的实现细节
6. ADT 操作四种类型
ADT 抽象数据类型名{
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
基本操作:<基本操作的定义>
}ADT 抽象数据类型名
Creators 构造器
从无到有,创建新的对象的方法。
Producers 生产器
从有到新,需要一个或多个该类型的现有对象作为输入,产生新对象的方法。
Observers 观察器
获取抽象类型中的对象,并返回其他类型的对象的方法。
Mutators 变值器
改变对象属性的方法。
// 有接口 SizeObject 这里 Car 实现了这个接口
public class Car implements SizeObject{
private Integer size;
// 构造器:构造方法
public Car(Integer size) {
this.size = size;
}
// 构造器:静态方法
public static Integer valueOf(Car car){
return car.getSize();
}
// 观察器:getter
public Integer getSize() {
return this.size;
}
// 生产器
public Car addCar(Car car1, Car car2) {
return new Car(car1.getSize() + car2.getSize());
}
// 变值器:setter
public void setSize(Integer size) {
this.size = size;
return;
}
}
7. RI 表示独立性
客户端仅仅依赖于公开方法的规约,与方法内部的实现无关,因此可以修改 MyString 的实现,而客户端无感。
Representation Independence 缩写 为 RI
public class Family {
// 注意:public 属性? 要观察下client的方法是否直接调用了这个 people 对象!
// 如果直接调用 则很大可能违反了 RI
public List<Person> people;
// public Set<Person> people;
public List<Person> getMembers() {
return people;
}
}
违法RI的实例:
遵守RI的实例:
8. 表示泄露 Rep Exposure
表示泄露,理解为,本应该隐藏起来的数据结构被 ADT 曝光
把不能泄露的字段设为
private final
进行防御式复制,如果任何类型是可变的,请确保您的实现不会将这些参数直接存储在 表示 中,或返回对 表示 的直接引用,因为这会导致 表示泄露 rep exposure 。(构造方法 和 getter)
使用 immutable 类型
9. AF
不扯概念直接上例子,简单而言就是如何用数据怎么表示这个实体类,从数据——实体类信息的一个映射
public class Complex {
private double real;
private double imaginary;
// 构造函数
public Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
// 获取实部
public double getReal() {
return real;
}
// 获取虚部
public double getImaginary() {
return imaginary;
}
// AF:将内部表示 (real, imaginary) 映射为复数 real + imaginary * i
// 即 AF(c) = real + imaginary * i
// 举例:如果 real = 3.0, imaginary = 4.0,那么 AF(c) = 3.0 + 4.0 * i
// RI:确保 real 和 imaginary 是有效的浮点数
// 对于复数,这里没有特别的约束,所以 RI 是 true
}
9.1 表示空间与抽象空间
AF的特性可以是:满射、非单射、未必双射
9.2 以注释形式写AF和RI
10. 类的六种关系
泛化关系
abstract class FoodItem {
public abstract void setVitamin();
public void setCaloric(){
}
}
public class Tomato extends FoodItem{
@Override
public void setCaloric(){
}
}
实现关系
依赖关系
public class Driver {
public void drive(Car car){
// 可能的一种实现
car.run();
}
}
可以将依赖关系理解为一种使用的关系,即“Use-a”的关系。
再简单一点理解,类 A 使用到了类 B,并且这种使用关系具有偶然性,临时性,是非常弱的关系,但是 B 类中的变化会影响到类 A。
关联关系
可以认为是多个实体的关联关系,有点类似数据库的外键
聚合关系
组合关系
11. 面向对象七原则
11.1 里氏代换原则 LSP
违反LSP的例1:超类方法不合适
违反LSP的例2:子类添加或修改方法
简单来说就是,子类不允许新增方法,不允许修改方法;因此子类要么继承抽象类,要么继承接口