软件构造第三章总结

数据类型

分为基本数据类型和对象数据类型两大类。
在这里插入图片描述

数据类型检查

分为动态类型检查和静态类型检查两大类。

静态类型检查

  1. 在编译阶段进行;
  2. 涉及到的错误类型有:语法错误、类名/函数名错误、参数数目错误、参数类型错误、返回值类型错误等;
  3. 静态类型检查是关于“类型”的检查。

动态类型检查

  1. 在运行阶段进行;
  2. 涉及到的错误类型有:非法的参数值、非法的返回值、越界、空指针等;
  3. 动态类型检查是关于“值”的检查。

可变性与不可变性

使用fianl关键字可以将可变类型转化为不可变类型。
final类无法派生子类
final变量无法改变值/引用
final方法无法被子类重写

可变类型

  1. 可变数据类型:拥有方法可以修改自己的值;
  2. 可变引用类型:拥有方法可以修改自己的引用;
  3. 可获得更好的性能,也适合在多个模块之间共享数据;
  4. 可通过防御式复制提高安全性,但可能造成大量的内存浪费。

不可变类型

  1. 不可变数据类型:一旦被创建,其值不能改变;
  2. 不可变引用类型:一旦确定其指向的对象,不能再被改变;
  3. 频繁修改会产生大量的临时拷贝,需要垃圾回收,但更“安全”。

代码快照图

通过描述程序运行时的内部状态,达到便于程序员之间的交流、刻画各类变量随时间变化、解释设计思路的目的。

基本类型

在这里插入图片描述

对象类型

在这里插入图片描述

不可变类型

  • 不可变对象:双线椭圆;
  • 不可变引用:双线箭头。
  • 不可变的引用,可以指向可变的值,可变的引用也可以指向不可变的值。

规约

包含输入/输出的数据类型、功能和正确性、性能,在开发者和客户端之间充当防火墙的角色。

规约的结构

  • 前置条件:对客户端的约束,在使用方法时必须满足的条件,基本放在@param中;
  • 后置条件:对开发者的约束,方法结束时必须满足的条件,基本放在@return和@throws中;
  • 如果前置条件满足了,后置条件必须满足。

可变方法的规约

  • 除非在后置条件里声明过,否则方法内部不应该改变输入参数。
  • 尽量避免使用可变对象,因为程序中可能有很多变量指向同一个可变对象,而开发者无法强迫类的实现体和客户端不保存可变变量的“别名”。

规约的强度

如果规约强度S2≥S1,那么意味着S2前置条件更弱,后置条件更强,如此S2就可替代S1。
如果用图来表示规约的强度,那么面积越小说明规约越强。
在这里插入图片描述

规约的确定性

  • 确定的规约:给定一个满足前置条件的输入,其输出是唯一的、明确的;
  • 欠定的规约:同一个输入可以有多个输出;
  • 非确定的规约:同一个输入, 多次执行时得到的输出可能不同。

如何设计好的规约

  • 内聚的,规约描述的功能应单一、简单、易理解;
  • 信息丰富的,不让客户端产生理解的歧义;
  • 足够强度的,既满足客户端基本需求,也必须考虑特殊情况;
  • 不足够强度的,太强的规约在很多特殊情况下难以达到;
  • 使用抽象类型,在规约里使用抽象类型,可以给方法的实现体与客户端更大的自由度。

抽象数据类型(ADT)

ADT四种操作

  • 构造器(Creator):方法构造新的对象,可能实现为构造函数或静态函数;
  • 生产器(Producer):方法从已存在的对象中创建一个新的对象;
  • 观察器(Observer):方法返回一个对象的相关值,该值与对象不是一个类型;
  • 变值器(Mutator):改变对象属性的方法,通常返回值为void。

设计ADT

  • 设计简洁、一致的操作;
  • 满足客户端的需要的同时降低操作难度;
  • 要么针对抽象设计,要么针对具体应用的设计。

表示独立性

ADT内部表示的变化不应影响规约和客户端。
除非ADT的操作指明了具体的前置或后置条件,否则不能改变ADT的内部表示。

不变量

  • 不变量:在任何状态下不变量均为true,由ADT 负责,与client端的任何行为无关,有助于保持程序的“正确性”,更容易发现错误。
  • 表示不变量:用于某个具体的“表示”是否是“合法的”。
  • 表示泄露:外部类的代码会改变该类内部的表示,不仅影响不变性,也影响了表示独立性,同样也无法在不影响客户端的情况下改变其内部表示。
    维护不变量防止表示泄露的方法:
  • 对于只在该类中使用的函数使用private关键字
  • 使用final关键字
  • 使用防御式复制,但不可避免地导致空间的浪费
  • 使用Collections.unmodifiableSet等方法

抽象函数AF与表示不变量RI

在这里插入图片描述
R:包含具体实现的值,ADT实现者关注;
A:包含设计时使用的值,客户端关注;

抽象函数AF:从R映射到A的抽象函数,即如何去解释R中的每一个值为A中的每一个值,具有如下特点:

  • 每一个抽象值都是由表示值映射而来 ,即满射;
  • 有些抽象值是由多个表示值映射而来,即未必单射;
  • 由于有些表示值是非法的,因此不是所有的表示值都能映射到抽象域中,即未必双射。

表示不变量RI:判断某个具体的“表示”是否是“合法的”。
由于RI应该是始终被满足的,所以在所有可能改变rep的方法内都要检查。

如何撰写AF和RI:

  • 首先选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值。
  • 不同的内部表示,需要设计不同的AF和RI;
  • 以注释的形式写在代码中。

面向对象的编程(OOP)

基本概念

对象

对象均具有状态和行为两个特征,比如一只狗是一个对象,他有名字、颜色、品种等状态,以及叫,摇尾巴等行为。
在Java中,字段指代状态,方法指代行为。

类是一个模板,描述了一类对象的行为和状态,每个对象都有一个类,其中定义了属性类型以及行为实现。

接口

  • 接口是ADT方法的集合,用于确定ADT规约。
  • 接口之间可以继承与扩展,一个类可以实现多个接口,一个接口可以有多种实现类。
  • 接口内无构造方法、字段和实现,其具体方法的实现在类中,用implements关键字连接接口。
  • 由于接口中的每个 方法在所有类中都要实现,所以可以使用default方法,在接口中统一实现某些功能,从而无需在各个类中重复实现它,以减少工作量。

接口的好处:

  • 安全性高:当客户端使用接口类型时,静态检查确保他们只使用由接口定义的方法,从而避免表示泄露;
  • 易于理解:客户端确切知道在哪查找ADT的规约;
  • 易于改变:通过添加和实现接口的类,从而添加新类型的实现。

继承和重写Override

继承

严格继承:子类只能添加新方法,无法重写超类中的方法,若要防止重写超类中的方法,超类中的方法需要用关键字final修饰。

重写Override

  • 重写的方法要有完全一样的函数声明,包括参数列表、返回类型、函数名称等。
  • 实际执行时调用哪个方法在运行时决定,执行动态类型检查;
  • 在重写的方法前,需要使用@Override关键字;
  • 若想在重写的函数中使用超类中的方法,使用super关键字;
  • 重写的时候,不要改变原方法的本意;
  • 父类的成员方法只能被其子类重写;
  • 子类访问权限不能比父类中的更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected;
  • 声明为final的方法不能被重写,声明为static的方法不能被重写,但是能够被再次声明;
  • 子类和父类在同一个包中,那么子类可以重写父类除声明为private和final的方法以外的所有方法;子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法;
  • 构造方法不能被重写;
  • 如果不能继承一个方法,则不能重写该方法。

多态和重载Overload

多态

  • 特殊多态:功能重载;
  • 参数化多态:泛型;
  • 子类型多态:包含多态。

重载Overload

  • 多个方法具有同样的名字,但有不同的参数列表或返回值类型;
  • 一种静态多态,在编译阶段时决定具体要执行哪种方法,根据参数列表进行最佳匹配,执行静态类型检查;
  • 必须改变参数列表(参数个数或类型不一样);
  • 可以改变返回类型、改变访问修饰符、声明新的或更广的检查异常;
  • 可在同一个类中或者在一个子类中被重载;
  • 无法以返回值类型作为重载函数的区分标准。

重写VS重载

在这里插入图片描述

泛型

泛型的本质是参数化类型,即将数据类型指定为一个参数。

  • 泛型类:其定义中包含了类型变量的类;
  • 泛型接口:定义中包含了类型变量的接口;
  • 泛型方法:定义中包含了类型变量的方法。

实现泛型接口的两种方式:

  • 泛型接口,非泛型的实现类:
    在这里插入图片描述
  • 泛型接口,泛型的实现类:
    在这里插入图片描述
    通配符(?)只在使用泛型的时候出现,不能再定义中出现,示例如下:在这里插入图片描述

ADT和OOP中的等价性

等价关系满足自反性、对称性和传递性。

不可变对象的等价性

  • 若AF映射到同样的结果,则等价;
  • 两个对象之间满足自反,传递、对称的关系;
  • 站在外部观察者角度发现二者没有区别。

== VS equals()

==:引用等价性,指向相同的内存地址;
equals():对象等价性,在自定义ADT时,需要重写Object的 equals(),可使用关键词instanceof,因为Object内的equals()判定的是引用等价性。

  • 等价的对象必须有相同的hashCode,不相等的对象,也可以映射为同样的hashCode,但性能会变差。
  • 重写Object中的equals()时,也要重写hashCode()。

对于基本数据类型,使用==判定是否相等;对于对象,使用equals()判定相等。

可变对象的等价性

  • 观察等价性:在不改变状态的情况下,两个可变对象是否看起来一致,即调用observer、producer、creater方法时不改变状态;
  • 行为等价性:调用对象的任何方法都展示出一致的结果。

对于不可变类型而言,观察等价性和行为等价性一致,因为其中没有mutator方法。

  • 对可变类型,实现行为等价性即可,即只有指向同样内存空间的对象才是相等的。
  • 因此,对可变类型来说,无需重写equals()和hashCode()方法,直接继承 Object的两个方法即可。
  • 如果一定要判断两个可变对象是否具有观察等价性,最好定义一个新的方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值