软件构造之 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 曝光

  1. 把不能泄露的字段设为 private final

  2. 进行防御式复制,如果任何类型是可变的,请确保您的实现不会将这些参数直接存储在 表示 中,或返回对 表示 的直接引用,因为这会导致 表示泄露 rep exposure 。(构造方法 和 getter)

  3. 使用 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:子类添加或修改方法

简单来说就是,子类不允许新增方法,不允许修改方法;因此子类要么继承抽象类,要么继承接口

11.2 单一职责原则 SRP

11.3 开闭原则 OCP

11.4 接口隔离原则 ISP

11.5 依赖倒置原则 DIP (也就是控制反转 IoC)