【软件构造】期末知识点

【考点 Equals】

  • ==是引用等价性 ;而equals()是对象等价性。 
    • == 比较的是索引。更准确的说,它测试的是指向相等(referential equality)。如果两个索引指向同一块存储区域,那它们就是==的。对于我们之前提到过的快照图来说,==就意味着它们的箭头指向同一个对象。
    • equals()操作比较的是对象的内容,换句话说,它测试的是对象值相等(object equality)。e在每一个ADT中,quals操作必须合理定义。
  • 基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 
    • 他们之间的比较,应用双等号(==),比较的是他们的值。 
  • 复合数据类型(类) 
    • 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
    • JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。 
    • 对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
  • HahCode:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

  当我们当向集合中插入对象时,就可以使用hashcode,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了。

 

【考点 函数规约】 requires与effects

声明式规约更有价值 ; 内部实现的细节不在规约里呈现,而放在代码实现体内部注释里呈现,例:

static String join(String delimiter, String[] elements)
effects: returns concatenation of elements in order, with delimiter inserted between each pair of adjacent elements // Declarative specs

 

更强的规约包括更轻松的前置条件和更严格的后置条件;

方法前的注释也是一种规约,但需人工判定其是否满足。

  • 参数由@param 描述
  • 子句和结果用 @return 和 @ throws子句 描述
  • 尽可能的将前置条件放在 @param 中
  • 尽可能的将后置条件放在 @return 和 @throws 中

 

 

【考点 ADT的四种类型】

  • Creators(构造器):
    • 创建某个类型的新对象,⼀个创建者可能会接受⼀个对象作为参数,但是这个对象的类型不能是它创建对象对应的类型。可能实现为构造函数或静态函数。(通常称为工厂方法)
    • t* ->  T
    • 栗子:Integer.valueOf(Object  obj):object → integer
  • Producers(生产器):
    • 通过接受同类型的对象创建新的对象。
    • T+ , t* -> T
    • 栗子:String.concat( )  :String x String → String
  • Observers(观察器):
    • 获取抽象类型的对象然后返回一个不同类型的对象/值。
    • T+ , t* -> t
    • 栗子:List.size( )   : List → int
  • Mutators(变值器):
    • 改变对象属性的方法 ,
    • 变值器通常返回void,若为void,则必然意味着它改变了对象的某些内部状态;当然,也可能返回非空类型 
    • T+ , t* -> t || T || void
    • 栗子:List.add( ) :List x int → List 

 

【考点 ADT的 AF与 RI】

在研究抽象类型的时候,先思考一下两个值域之间的关系:

  • 表示域(rep values)里面包含的是值具体的实现实体。一般情况下ADT的表示比较简单,有些时候需要复杂表示。 
  • 抽象域(AF)里面包含的则是类型设计时支持使用的值。这些值是由表示域“抽象/想象”出来的,也是使用者关注的。

R->A的映射特点:

  • 每一个抽象值都是由表示值映射而来 ,即满射:每个抽象值被映射到一些rep值
  • 一些抽象值是被多个表示值映射而来的,即未必单射:一些抽象值被映射到多个rep值
  • 不是所有的表示值都能映射到抽象域中,即未必双射:并非所有的rep值都被映射。

在描述抽象函数和表示不变量的时候,注意要清晰明确:

  • 对于RI(表示不变量),仅仅宽泛的说什么区域是合法的并不够,你还应该说明是什么使得它合法/不合法。
  • 对于AF(抽象函数)来说,仅仅宽泛的说抽象域表示了什么并不够。抽象函数的作用是规定合法的表示值会如何被解释到抽象域。作为一个函数,我们应该清晰的知道从一个输入到一个输入是怎么对应的。

 

【考点:黑盒、白盒框架】

  • 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。
  • 为了增加代码的复用性,可以使用委派和继承机制。同时,在使用这两种机制增加代码复用的过程中,我们也相应地在不同的类之间增加了关系(委派或继承关系)。而对于一个项目而言,各个不同类之间的依赖关系就可以看做为一个框架。一个大规模的项目可能由许多不同的框架组合而成。

【白盒框架】

  • 白盒框架是基于面向对象的继承机制。之所以说是白盒框架,是因为在这种框架中,父类的方法对子类而言是可见的。子类可以通过继承或重写父类的方法来实现更具体的方法。
  • 虽然层次结构比较清晰,但是这种方式也有其局限性,父类中的方法子类一定拥有,要么继承,要么重写,不可能存在子类中不存在的方法而在父类中存在。
  • 软件构造课程中有关白盒框架的例子:

 【黑盒框架】

  • 黑盒框架时基于委派的组合方式,是不同对象之间的组合。之所以是黑盒,是因为不用去管对象中的方法是如何实现的,只需关心对象上拥有的方法。
  • 这种方式较白盒框架更为灵活,因为可以在运行时动态地传入不同对象,实现不同对象间的动态组合;而继承机制在静态编译时就已经确定好。
  • 黑盒框架与白盒框架之间可以相互转换,具体例子可以看一下,软件构造课程中有关黑盒框架的例子,更改上面的白盒框架为黑盒框架:

【两者对比】

  • 白盒框架利用subclassing:
    • 允许扩展每一个非私有方法
    • 需要理解父类的实现
    • 一次只进行一次扩展
    • 通常被认为是开发者框架
  • 黑盒框架使用委派中的组合composition:
    • 允许在接口中对public方法扩展
    • 只需要理解接口
    • 通常提供更多的模块
    • 通常被认为是终端用户框架,平台

 【LSP】

  • 里氏替换原则的主要作用就是规范继承时子类的一些书写规则。其主要目的就是保持父类方法不被覆盖。子类的规约变强!
  • LSP依赖于以下限制:
    • 前置条件变弱或者不变
    • 后置条件变强或者不变
    • 不变量要保持或增强
    • 子类型方法参数:逆变(规约变强,前置变弱 反着变)
    • 子类型方法的返回值:协变(规约变强,前置变强 协同变化)
    • 异常类型:协变
  • 数组是协变的,向上转型是成立的
Fruit[] apples=new Apple[size];  
  •  泛型是类型不变的(泛型不是协变的)。举例来说 
    • ArrayList<String> 是List<String>的子类型 
    • List<String>不是List<Object>的子类型
  •  类型擦除的结果: <T>被擦除 T变成了Object

【Throwable】

  • 健壮性:输入错误,给出确定的正常的输出,倾向于容错;正确性:出现错误报出错误,倾向于error。
  • 对外的接口,倾向于健壮性;对内的实现,倾向于正确性。

  • 运行时异常:由程序员处理不当造成,如空指针、数组越界、类型转换
  • 其他异常:程序员无法完全控制的外在问题所导致的,通常为IOE异常,即找不到文件路径等
  • checked异常:
    • checked exception是需要强制catch的异常,你在调用这个方法的时候,你如果不catch这个异常,那么编译器就会报错,比如说我们读写文件的时候会catch IOException,执行数据库操作会有SQLException等。

  • unchecked异常:
    • 这种异常不是必须需要catch的,你是无法预料的,比如说你在调用一个 list.szie()的时候,如果这个list为null,那么就会报NUllPointerException,而这个异常就是 RuntimeException,也就是UnChecked Exception
    • 常见的unchecked exception:JVM抛出,如空指针、数组越界、数据格式、不合法的参数、不合法的状态、找不到类等

 

    

【GC的四种策略】                                          

  • 引用计数
    • 基本思想:为每个object存储一个计数RC,当有其他 reference指向它时,RC++;当其他reference与其断开时,RC--;如 果RC==0,则回收它。
    • 优点:简单、计算代价分散,“幽灵时间”短 为0
    • 缺点:不全面(容易漏掉循环引用的对象)、并发支 持较弱、占用额外内存空间、等
  • Mark-Sweep(标记-清除)算法
    • 基本思想:为每个object设定状态位(live/dead)并记录,即mark阶段;将标记为dead的对象进行清理,即sweep可阶段。
    • 优点:可以处理循环调用,指针操作无开销,对象不变
    • 缺点:复杂度为O(heap),高 堆的占用比高时影响性能,容易造成碎片,需要找到root

  • Copying(复制)算法
    • 基本思想:为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
    • 优势:运行高效、不易产生内存碎片
    • 缺点:复制花费大量的时间,牺牲内存空间

  • Mark-Compact(标记-整理)算法
    • 基本思想:为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

 

  • 年青一代使用copying算法,年好易贷使用Mark sweep和mark-compact算法

【死锁】

  • 产生死锁的四个必要条件:
    • 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
    • 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
    • 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
  • 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
  • 防止死锁的方法:
    • 加锁顺序:当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。这种方式是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁,但总有些时候是无法预知的

    • 使用粗粒度的锁,用单个锁来监控多个对象
      • 对整个社交网 络设置 一个锁 ,并且对其任何组成部分的所有操作都在该锁上进行同步。
      • 例如:所有的Wizards都属于一个Castle,  可使用 castle 实例的锁
      • 缺点:性能损失大;
        • 如果用一个锁保护大量的可变数据,那么久放弃了同时访问这些数据的能力;
        • 在最糟糕的情况下,程序可能基本上是顺序执行的,丧失了并发性

 

【用注释形式撰写测试策略】

 

【测试覆盖度】

代码覆盖度:已有的测试用例有多大程度覆盖了被测程序

代码覆盖度越低,测试越不充分但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高

分类:函数覆盖 + 语句覆盖 +分支覆盖 + 条件覆盖 + 路径覆盖

测试效果:路径覆盖>分支覆盖>语句覆盖

测试难度:路径覆盖>分支覆盖>语句覆盖

路径数量巨大,难以全覆盖

 

【snapshot】在Runtime,code level,moment

【SOLID】

设计模式前五个原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:

  单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲(实现效果),它告诉我们要对扩展开放,对修改关闭。

 

posted @ 2018-06-22 19:16  HongmingYou  阅读(1292)  评论(0编辑  收藏  举报