软件构造期末复习总结(二)

第七节 面向对象的编程(OOP)
静态/实例方法
在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段

编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然可能有多个实例,但这些实例共享该内存

接口(Interface)
接口之间可以继承与扩展,一个类可以实现多个接口,一个接口可以有多种实现类

接口:确定ADT规约; 类:实现ADT

Java的接口中不能含有constructors,但是从Java 8开始接口中可以含有static工厂方法,可用其替代constructors

default
通过default方法,可以在接口中统一实现某些功能,无需在各个类中重复实现它。好处是以增量式为接口增加额外的功能而不破坏已经实现的类

重写(Overriding)
严格继承:子类只能添加新方法,无法重写超类中的方法

如果想要一个java中方法不能被重写,必须要加上前缀final

父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
重写时,可以利用super()来复用父类型中函数的功能

抽象类(Abstract Class)
抽象方法:只有声明没有具体实现的方法。用关键词abstract来定义

抽象类:如果一个类含有至少一个抽象方法,则被称为抽象类

接口:一个只含有抽象方法的抽象类

如果某些操作是子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写

接口和抽象类都不能实例化!

多态、子类型、重载(Polymorphism, subtyping and overloading)
(考试经常出现重载和重写的对比考察)

多态的三种类型
特殊多态(Ad hoc polymorphism):重载
参数化多态(Parametric polymorphism):泛型
子类型多态、包含多态(Subtyping):继承
特殊多态和重载(Overloading)
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型

重载是一种静态多态,根据参数列表进行"最佳匹配",进行静态类型检查

重载的解析在编译阶段,与之相反,重写的方法是在运行阶段进行动态类型检查

参数列表必须不同
相同/不同的返回值类型
相同/不同的public/private/protected
可以声明新的异常
Overloading和Overriding的对比

参数多态和泛型(Generic)
泛型擦除:运行时泛型类型消除(如:List运行时是不知道String的),所以,不能使用泛型数组(如: Pair < String >[] foo = new Pair < String >[42]; 是错误的!不能被编译!)

如下是一个错误的实例:

List a; List b; a = b;

通配符(Wildcards),只在使用泛型的时候出现,不能在定义中出现。 如:List< ? extends Animal >

?extends T 和 ?super T 分别表示T和它的所有子/父类

子类型多态、继承
重写时,子类的规约要强于父类的规约(更弱的前置条件,更强的后置条件)
子类的可见性要强于父类(即父类如果是public,子类不能为private)
子类不能比父类抛出更多的异常
(详情见LSP原则)

注:Java无法检测1,但是可以检测出2、3

子类型多态:不同类型的对象可以统一的处理而无需区分。

instanceof
instanceof()判断对象运行时的类型

注:其父类也会判为true,如 a instanceof Object 始终为true

getclass()获取当前类型

List不是List的父类
List是ArrayList的父类
List<?> 是 List的父类
注:重写equal()方法时,需要注意参数类型,必须也是Object类型

第八节 ADT和OOP中的相等性
相等关系
相等关系是一种等价关系,即满足自反、对称、传递

可以用"是否为等价关系"来检验equals()是否正确

Immutable类型的相等
判相等要从A空间来看(用户角度) AF映射到相同结果,则等价

站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。

== vs. equals()
== 表示的是引用等价性(一般用于基本数据类型的相等判定)
equals()表示的是对象等价性 (用于对象类型相等判定)
在自定义ADT时,需要重写Object 的 equals() 方法

equals()方法的实现
在Objects中实现的缺省equals()是在判断引用相等性(相当于==)

用instanceof操作可以判断对象是否是一种特殊的类型(用instanceof是一种动态类型检查,而不是静态类型检查)

注意:不能在父类中用instanceof判断子类类型

等价的对象必须拥有相同的hashCode;不相等的对象也可以映射为同样的hashCode,但是性能会变差
重写equals方法必须要重写hashCode方法(除非能保证你的ADT不会被放入到Hash类型的集合中)
mutable类型的相等
观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致

行为等价性:调用对象的任何方法都展示出一致的结果

对于mutable类型来说,往往倾向于实现严格的观察等价性(但是在有些时候,观察等价性可能导致bug,甚至破坏RI)

注意:如果某个mutable的对象包含在Set集合类中,当其发生改变后,集合类的行为不确定!

Collections 使用的是观察等价性,但是其他的mutable类(如StringBuilder)使用的是行为等价性

对mutable类型,实现行为等价性即可。也就是说只有指向同样内存空间的objects,才是相等的,所以对mutable类型来说,无需重写这两个函数,直接调用Object的两个方法即可。(如果一定要判断两个对象"看起来"是否一致,最好定义一个新方法,e.g. similar() )

immutable类型必须重写equals() 和 hashCode()
mutable类型可以不重写,直接继承自Object
clone()
clone()创建并返回对象的一个copy

浅拷贝:对于基本数据类型,无影响;对于数组或对象数据类型,浅拷贝只是将内存地址赋值给了新变量,它们指向同一个内存空间。改变其中一个对另一个也会产生影响。

Java中的clone实现的是浅拷贝。

要避免一些问题,建议使用深拷贝。

Autoboxing
Integer 和 int 注意区别

flase

(Numbers between -128 and 127 are true.)

第九节 面向复用的软件构造技术
(重点:LSP和组合与委托)(考试大部分为小题,LSP会出大题)

复用的类型
软件复用:最主要的是代码复用,但也有其他方面。

Source code level:methods, statements, etc Module level:class and interface Library level:API Architecture level:framework

白盒复用:源代码可见、可修改和扩展(对应继承)

黑盒复用:源代码不可见,不能修改,只能通过API接口使用(对应委托)

代码复用
即直接复制代码(不推荐)

类的复用
inheritance 继承 delegation 委托

继承能做的事委托也能做,继承要求严格父子关系

框架(framework)
框架:一组具体类、抽象类、及其之间的连接关系

开发者根据framework的规约,填充自己的代码进去,形成完整系统

API和框架的区别:主控端在用户/框架

*LSP(重点)
子类型多态:客户端可以用统一的方式处理不同类型的对象。

LSP原则
能被Java静态类型检测检测出的:

子类型可以增加方法,但不可以删除方法
子类型需要实现抽象类型中的所有未实现的方法
子类型中重写的方法必须返回相同的类型或者子类型(满足协变)
子类型中重写的方法必须使用同样类型的参数(或符合逆变的参数)
子类型中重写的方法不能抛出额外的异常(协变)
不能被静态类型检测出的:

更强的不变量
更弱的前置条件
更强的后置条件(与上条综合即规约更强)
协变:父类型->子类型:越来越具体

反协变(逆变):越来越抽象

注意:Java不支持反协变!Java识别其为重载(而非重写)

数组满足协变。

泛型中的LSP
泛型不满足协变 List不是List的子类型

Object是所有泛型的父类,List<?>是List的父类

(该图为考点!)

委托(Delegation)
Interface Comparator< T >
int compare(T o1, T o2): Compares its two arguments for order

如果你的ADT需要比较大小,或者要放入Collections或Arrays进行排序,可以实现Comparator接口并且override compare()函数

另一种方法:让ADT实现Comparable接口,然后override compareTo() 方法

与使用Comparator的区别:不需要构建新的Comparator类,比较代码放在ADT内部

委托
委托/委派:一个对象请求另一个对象的功能。

如果子类只需要复用父类中的一小部分方法,可以通过委托机制调用。

委托是复用的一种常用形式。(CRP原则:尽量使用委托进行复用)

Use 使用:通过方法的参数传递(use_a)
Association 关联:通过类的属性传递(has_a)
class B{
void b(A a){ //use 使用

}
}
1
2
3
4
5
class B{
A a; //Association 关联

}
1
2
3
4
composition/aggregation 组合/聚合(可认为是Association的两种具体形态)

聚合运行时可更改绑定对象(较弱的关联)

聚合B类销毁时,A类可能不会销毁(可能还有指向其的指针);组合B类销毁时,A类同时被销毁

第十节 面向可维护性的软件构造技术
(本章重点:SOLID、正则表达式)

可维护性度量指标
圈复杂度(Cyclomatic Complexity)、代码行数、可维护性指数(MI)、继承的层次数、类之间的耦合度、单元测试的覆盖度

模块化编程
高内聚(High cohension) 低耦合(Low coupling)

耦合(Coupling):不同模块之间的相互依赖性

内聚(Cohension):模块内功能和职责的一致性

耦合和内聚之间的权衡:

即不能同时高/同时低

SOLID设计原则
SRP(单一责任原则)
OCP(开放-封闭原则)
LSP(Liskov替换原则)
DIP(依赖转置原则)
ISP(接口聚合原则)
SRP(单一责任原则)
把类拆分,使得每个类只完成一个功能。(不应该有多于一个的原因使得类发生变化)

SRP是最简单的原则,却是最难做好的原则

OCP(开放/封闭原则)
对扩展性的开放:模块的行为应该是可扩展的

对修改的封闭

关键的解决方案:抽象技术

例:如果有多种类型的Server,那么针对每一种新出现的Server,不得不修改Server类的内部具体实现;

通过构造一个抽象的Server类:AbstractServer,该抽象类中包含针对所有类型的Server都通用的代码,从而实现了对修改的封闭;当出现新的Server类型时,只需从该抽象类中派生出具体的子类ConcreteServer即可,从而支持了对扩展的开放。

LSP(Liskov替换原则)
详情见第九节LSP原则

ISP(接口隔离原则)
尽量和专用接口连接(客户端不应依赖于它们不需要的方法)

DIP(依赖转置原则)
尽量依赖抽象类而不是具体类

也就是delegation的时候,通过interface来建立联系,而非具体子类

正则表达式
*:重复0-多次

|:选择 a|b

?:0次或1次 x ::= y ? x为y或空串

[a-c]:‘a’ | ‘b’ | ‘c’

[^a-c]:‘d’ | ‘e’ | ‘f’ …(相当于补)

Parse Tree

正则语法(Regular grammar)
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点

正则表达式:左侧为非终结符,右侧没有非终结符

注意有的字符需要进行转义

Java中的正则表达式
Pattern是对regex正则表达式进行编译之后得到的结果

Matcher:利用Pattern对输入字符串进行解析

Matcher对象只能通过Pattern静态方法创建,不能new

第十一节 设计模式
分为以下三类模式:创建型模式、结构型模式、行为型模式

创建型模式(Creational patterns)
*工厂方法模式(Factory Method pattern)
当client不知道要创建哪个具体类的实例,或者不想再client代码中指明要具体创建的实例时,用工厂方法。

工厂模式:将创建一个对象的方法委托给另一个类(工厂类)来实现

Client用工厂方法来创建实例,得到的实例类型是抽象接口而非具体类

静态工厂方法(在工厂方法前加static):既可以在ADT内部实现,也可以单独创建工厂类

该设计模式是OCP(扩展/开放原则)的一个体现。

优点:实现信息隐藏

缺点:需要额外创建工厂类,程序更复杂

结构型模式(Structural patterns)
适配器模式(Adapter)
将某个类/接口转换为client期望的其他形式(主要解决接口不匹配问题)

通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类

装饰器模式(Decorator)
对一个类的功能进行扩充(实现特性的组合)

装饰器在运行时组合特性;继承在编译时组合特性

行为型模式(Behavioral patterns)
*策略模式(Strategy)
使用功能的时候不访问功能实现,访问接口(在多个功能间灵活切换)

考试时一般要做的:1. 抽象出一个接口类 2. 调用接口

*模板模式(Template Method)
共性的操作步骤在抽象类中公共实现,差异化的步骤在各个子类中实现

使用继承和重写来实现模板模式

模板模式在框架中应用广泛

抽象类中有一些方法用final来修饰,这些方法即为模板方法

迭代器(Iterator)
将集合类的迭代操作委托给迭代器来实现。

让自己的集合类实现 Iterable 接口,并实现自己的独特 Iterator 迭代器 (hasNext, next, remove) ,允许客户端利用这个迭代器进行显式或隐式的迭代遍历:

*Visitor模式
把类中的某些功能委托给别人实现(实现功能时要反过来用到原来的类)

Visitor vs Iterator

迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能delegate到外部的iterator对象。

Visitor:在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT

迭代器和Vistor模式结构相同,只是方法不同(本质上无区别)

Strategy vs visitor

自己的独特 Iterator 迭代器 (hasNext, next, remove) ,允许客户端利用这个迭代器进行显式或隐式的迭代遍历

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值