本期是软件构造第4-8讲的自我总结,这部分是考试中最重要的一部分,分值占比最大,从软件构造的基本组成:数据和行为说起,再将二者联合构成ADT,之后讲述了ADT的实现方法:OOT,最终回归到对象间的相互关系中来。不得不感叹MIT教学的逻辑性之强,知识连贯性之高。
这一讲从软件最基本的组件:数据讲起,并根据数据类型的划分进行不同的检验
不可变
栈中分配内存
int/long/boolean/double/char
对象间可以用继承(extends)形成层次结构
静态和动态的区分关键在于阶段。静态往往是在程序运行前的阶段就已经确定,比如静态变量、静态检查亦如此,无需在程序运行的时候就可以对代码中的数据潜在错误进行检测;而动态恰恰相反,也正如其名,是需要程序在运行的动态环境中进行检测的。这个本质我们后续会经常用到。而程序中某些bug并不会引起检查错误,但是会导致得到的结果并不尽我们所意。
就重要性而言:静态检查》》动态检查》》无检查
其实正如静态的定义,由于这类错误是代码中类型/符号的匹配是显式的标出的,因此可以用这种方法进行辅助定义
改变一个变量的值和改变一个变量的区别:
不变数据类型:一旦被创建其值不可改变;如果是引用类型,一旦确定指向的对象,不能再改指向
编译器进行静态类型检查时,如果final变量首次赋值后发生改变则提示错误
有关final的注意事项:
这里有一个典型的一对例子:String和StringBuilder。String是不变量,StringBuilder是变量
可变类型的优势:最少化拷贝,不需垃圾回收,提高效率
如何安全的使用可变类型?设计为局部变量,免去共享的问题;或者只有一个引用
给客户端一个全新的对象
不可变类型不需要防御式拷贝
软件中的视图
这里主要是各种概念和画法,我截取了几条规则:
引用不可变但是指向的值可变
引用可变也可指向不可变的值
迭代器:
规约是程序与客户端达成的一致,给双方都确定了责任,调用时双方都要遵守
规约可以起到隔离的作用,客户端不需要知道实现,实现者不需要知晓如何被调用。
前置条件:对客户端的约束,使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
前置条件满足,则后置条件必须满足
静态类型声明是一种规约,据此进行静态类型检查;方法前注释也是一种规约,但需人工判定
更强的规约:前置条件更弱或相等;或满足同一前置条件下,后置条件更强或相等
图像化规约
规约设计原则
是否使用前置条件取决于对于输入参数检查的代价以及方法的适用范围。如果方法是private的可以适当不用前置条件,如果是public则需要设计较好的前置条件
前两讲针对两大组成部分:数据以及数据的操作(方法)分别进行了理论性的阐述和设计思想。而ADT就是这二者的集大成者,通过定义一系列数据的组合及其对应的数据操作,使得开发者可以更好的设计程序,将实际问题抽象出来。
T :抽象类型;
t :其他类型;
+ :表明类型可能出现至少一次;
* :出现≥0次;
| :或
可能实现为构造函数或静态函数
实现为静态方法的构造器通常为工厂方法
String.valueof(Object) :一种特殊的creator
concat()
size()
通常返回void,改变了对象的某些内部状态,也可以返回非空类型
含义:client使用ADT时无需考虑其内部如何实现,ADT内部表示变化不应影响外部规约和客户端
针对creator:构造对象后用obeserver去观察是否正确
针对observer:用其他三类方法构造对象,然后调用observer判断观察结果是否正确
针对producer:produce新对象后用observer判断结果是否正确
针对mutator:用observer查看调用mutator后的结果是否正确
一个良好的数据结构需要始终保持其不变量
不变量就是一个程序任何运行时状态都为true的性质
不变性就是一个不变量的invariant。这里的invariant有一点抽象,其实是指一个抽象类型的一些一直保持的性质,比如刚才的例子,一个immutable 对象,他的一个invariant就是它的不变性。一个ADT保持了它自身的invariant就是指有它自身来负责这些性质,与client的行为无关。
保持invariant的意义在于让程序更容易被检测出错误
ADT的一个最重要的invariant就是保持不变性(immutability)和避免表示泄露
用来衡量某个具体的”表示“是否是”合法的“;
或者看作所有合法表示值得合集
或看作一个描述了”合法“表示值的条件
应该在所有可能改变rep的方法内部都检查(checkRep());Observer建议也检查
记录表示中的field何为有效
表示空间(R):实现者看到和使用的值
抽象空间(A):client看到和使用的值
抽象函数(AF):R和A之间映射关系的函数,
选定某种特定的表示方式后,进而指定某个子集的合法性(RI),并为该子集中的每个值做出解释(AF)
精确记录AF:如何解释每个值
学习了ADT之后,使用面向对象的编程方法来实现ADT的创造
类中的static和instance的变量和方法:静态方法无法直接调用非静态成员,调用静态方法不需要创建对象。
接口中只有方法的定义而没有实现,接口之间可以继承与拓展。
一个类可以实现多个接口而一个接口可以有多种实现类
接口:确定ADT规约;类:实现ADT
通过default方法,在接口中统一实现某些功能,从而无需在各个类中重复实现。
只有定义没有实现,抽象类不可以实例化,继承某个抽象类的子类在实例化时,所有父类中的抽象方法必须已经实现
方法的重写中的参数、返回类型都保持不变
如果父类型中的某个函数实现体为空意味着所有子类型都需要这个功能
重写之后利用super()复用父类型中函数的功能。对于一个构造器来说,super必须放在方法的第一部分
override在run-time进行动态检查
overload在编译阶段进行静态类型检查
而针对重载和重写的区别,我们可以看这个表格:
泛型类
泛型接口
泛型方法
子类型的规约不能弱化超类型的规约
如果AF映射到同样的结果,则等价
如果对两个对象调用任何相同的操作都会得到相同的结果,则认为两个对象等价
对基本数据类型使用==判定相等(判断对象时看是否指向内存里的同一段空间)
对对象类型使用equals()
在使用时,可以先用instanceof判断对象类型,再判断属性值
instanceof是动态类型检查
程序中多次调用同一对象的hashCode方法,都要返回相同值;
等价的对象必须有相同的hashCode
equals()比较抽象值、用行为等价性判断
hashCode()将抽象值映射至整数
两个函数都应该重写
对于可变类型,往往倾向于使用严格的观察等价性
可变类型行为等价需要引用相等
观察等价性需要通过观察器判断
equals()比较引用、用行为等价性判断
hashCode()将引用映射为一个函数
不应重写这两个函数
本文发布于:2024-01-28 08:19:37,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17064011836064.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |