提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
博客5写的有点太长了,但是其实复习才刚刚开始,需要记录的太多了,这里从OOP的复习开始,继续记录吧!
一、OOP 复习
1.1 接口 interface
类与接口:
接口之间可以继承与扩展
一个类可以实现多个接口
一个接口也可以有多种实现类
接口的样子:
可以看到接口不具备构造函数,所以调用实现类的构造函数(一个接口是有很多个实现类的),比如:
但是其实观察到,这里需要调用一个实现类,这样就其实是把某个具体实现的实现类给暴露出来了,用户端就知道了我们的一个类,这样当然不行!比如下面这样:
这里我们就需要用后面要学的静态工厂方法了,我们可以在接口的静态方法中将这个实现类的构造函数进行封装,这样就看不到了。客户端需要构造一个新的对象直接用构造方法就行了。
1.2 接口中的default方法
default方法可以在接口中直接写出它的实现,在实现类中就不用再写这个实现了,多个实现类都可以使用这个接口中的方法。
接口中的static方法也再接口中写出,不需要在实现类中再写,调用创建时可以直接调到接口中的方法·
1.3 interface的应用与书写的例子
- 首先interface的第一个方法不能是构造方法,接口不能有接口方法,可以用静态方法
- 接口中第二个方法在实现类中没有进行重写,接口中的每个都必须要被实现!不过你可以新添加方法
- union方法不能返回一个arrayset方法,这出现了表示泄露
- add方法是Mutator这不符合这个类的spec
1.2 继承的重写
接口是可以继承接口的,也可以是一个接口继承多个接口
注意区分几个术语:
接口与接口之间是扩展关系 :extends
接口实现类是:implements
类与类之间的继承关系:也是extends关键字,不过这个被称为Inheritance
重写是函数名、函数参数类型数量次序、函数返回值都一致
调用子类型的方法还是子类型的方法,在运行时 动态地决定
PS:(特殊情况)
- 如果子类中没有重写,那么会直接调用父类的方法
- 如果父类中没有写,那么它们之间没有共性,需要子类自行实现
- 可以在子类中override的方法中实现特殊的功能,同时调用父类的方法,例子:
- 如果在构造方法中调用super,那么super只能在第一行,如果不是构造函数的第一行那么不会通过静态检查
1.2 抽象类
形容词abstract
在class前面加上abstract意味着是抽象类,但是至少有一个方法是abstract的
抽象的类意味着这个类在客户端不能被直接实例化的
和接口是一样的,它的实现需要先实现子类,然而在子类中我们需要将抽象方法实现
1.3 多态 、子类型、重载
1.3.1 三种多态
- 功能重载
- 泛型
- 子类型多态 (最重要!!)
1.3.2 重载
参数列表类型不同或者返回值不同,在静态检查的时候进行参数匹配的时候,在编译阶段就决定了要执行哪个方法
- 必须要求参数列表类型不同
- 返回值类型、public等修饰、异常都是可以相同可以不同的
- 可以在同一个类中进行重载也可以在不同的类进行重载
例子分析:
1、2、3都是重载,第四个需要参数类型不同!只改变它的参数名是没有用的
第五个只改变了返回值类型,没有改变参数列表,也不选
例子分析:
重载用需要的是静态类型检查,只看运行之前等号前面的类型,因为这时候他还没有被赋值
对比总结:
1.4 重写equals方法
这部分在我的blog3中已经有所体现,不过这里需要补充的三点:
- 任何对象equals(null)都返回false
- 作为重写方法,equals的参数应该是object,不能被改变
- Mutable的类可能实现的是观察等价性也有可能是行为等价性,常用的要注意Date和List都是行为等价性,例题如下:
二、可复用性的度量、形态与外部表现
- 源代码级别的复用
- 模块级别的复用:类/抽象类/接口
- 库级别的复用:API/包
- 系统级别的复用:框架 framework
如何评估是否适合复用:
- 小、简单——》复用范围大
- 与标准兼容
- 灵活可变
- 可扩展
- 泛型,参数化
- 模块化
- 变化的局部性——>表示独立性,不要让只改变一个模块,很多地方需要改动
- 稳定
- 丰富的文档和帮助
复用力度:
三、子类型多态
子类型多态:客户端可用统一的方式处理不同类型的对象
用子类型可以无条件地取代任何一个父类型
LSP原则
- 子类型可以增加方法,但不能删除
- 子类型需要实现抽象类型中的所有未实现方法
- 子类型中重写的方法必须有相同或者子类型的返回值或者符合co-variance的参数 (协变)
- 子类型中重写的方法必须使用同样的参数或者符合contra-variance (逆变) 的参数
- 子类型中重写的方法不能抛出额外的异常,满足协变
- 更强的不变量
- 更弱的前置条件
- 更强的后置条件
PS:父类型和子类型的可变与不可变类型应该一致
注意这里我们对重写的要求和上面所说的重写需要完全一致不同,这里重新规定了,重写只需要返回值满足协变, 参数满足逆变(JAVA不支持逆变) 以及不抛出额外的异常,满足协变即可。
也就是说,子类型重写的返回值应该更具体,抛出的异常也应该更具体(类变得具体,返回值和异常也变得具体,这就是协变)
基础类型的协变
可以把子类型的复制给父类型的
但是如果已经定义赋值为子类型,比如Integers就固定为Integers,就不能再传入一个double的
泛型的协变
泛型不存在协变关系,因为类型擦除,所以传入的泛型都被看做Object
就像下面的List是ArrayList的父类
但是List不是List的父类,两者已经没有关系了
因此我们可以看到这里的错误,list已经不是list的子类型了,不能直接赋值,认为将子类型赋值给父类型
integer是number的子类型,但是放到Box里面因为泛型不存在协变,Box和Box
但是我们想要利用泛型的协变,这就需要通配符了,而不能用object
通配符的其他两个用法:
? super A : A 的父类型都可以传入
?extends A : A 的子类型都可以传入
相关题目练习:
答案:abcd
解析:A很明显,List和List没有关系,并不是List的子类
B和D使用了通配符,需要在方法参数那里用通配符才可以,然后根据通配符的范围传入参数
而C选项,ArrayList 和list的泛型不一致,不存在继承关系
四、delegation 委托
委派/委托:一个对象请求另一个对象的功能
委派是对象与对象之间的关系!! ,在构造函数中,传入了a的对象,建立了delegation
需要注意的是继承则是类与类之间的关系!!!
继承是比委派更强的关系,在UML中,继承用的是is_a ,而委派则是has_a或者是use_a
两种不同类型的delegation
依赖性委托(Dependence):临时性的delegation
只是方法参数传递,而没有属性
关联关系(Association):永久性性的delegation(包括composition和aggregation)
在属性中就传入了
可以通过参数传入后赋值,也可以没有参数直接在方法中new
不推荐的composition(组合),更强的Association
这是一种更强的关联关系,在属性中就写固定了,而不是像关联关系一样在构造函数中赋值,在属性中就已经把子类型写了出来,修改时很难修改
aggregation(聚合),更弱的Association,可以动态变化
通过传入参数
四个概念的关系:
五、framework 框架的复用
分为白盒框架和黑盒框架
白盒框架:继承和重写——模板方法
黑盒框架:delegation
两者的关系与不同,黑盒是终端在框架中运行,而白盒实际上因为父类是抽象的类,运行的是子类型
例子,白盒框架示例:
子类型继承了父类型,子类型重写了方法,在客户端调用时候直接调用了子类型
黑盒框架:通过关联关系,设置一个外部的对象textToshow,需要进行扩展的方法都在这个外部对象中进行实现,这个外部对象也可以是外部接口,后续可以对这个外部的接口进行实现。
客户端调用的时候只需要传入一个外部对象就行了
六、Adapter 适配器
复用一个接口不匹配的API
适配器是实现一个接口得到的,面向接口的编程,具体例子:
六、Decorator 装饰器
Decorator下面方法是个性的方法
基础方法,共性功能的例子:
对此,后面装饰器实现Stack接口的例子,注意,这里的基础功能是直接调用的基础类
具体的子类,继承StackDecorator,仍然是实现Stack接口,可以添加新的Rep并且实现自己独有的功能
客户端处的调用:
例题:
答案:BD
解析:首先明确的是,其实s是可以传入任何Stack子类型的,不过就是传入Undostack没有必要再进行装饰。A是对的,B是错的。然后是C选项,,运行时当然是secureStack。D选项,这进行了三个new,实际上是有四个Stack存在,修饰之前的和修饰之后的不是一个stack
七、Strategy 策略
要实现多个算法,有A和B两个算法实现。面向接口抽象的接口编程,接口Strategy
客户端在Context处,选择哪一种算法
客户端调用例子:在这里我们传入的是两个策略子类型(也可以用静态工厂方法隐藏)
传入后在Context类里面得到子类型参数,可以进行委派,例:
它的子类型具体实现,重写了接口的方法:
八、Template Method 模板方法
用于白盒框架,只实现一棵继承树,使用继承和重写实现模板模式,大概的继承关系如下:
其中要注意的是,templateMethod()方法是公共的,不能修改,所以应该用final修饰,其中#step1 和 #step2 等三个方法是抽象abstract方法,在后续的子类里面进行实现。
例子:
模板:
子类重写:
例题:
答案:AD
解析:
主要是A选项,它应该是抽象类,如果是接口就都不能实现了(当然题目的意思应该是没考虑default方法和静态方法),我们想要实现公共的方法就需要是abstract类
九、Iterator 迭代器模式
代码实现:
想要自己的类Pair类可以实现迭代操作,就需要让他实现Iterable接口,让他具有那个可以返回一个迭代器的方法,iterator。这时候返回的迭代器可以是自己写的迭代器·。而这个PairIterator类是实现iterator接口,实现三个基本方法·。
十、补充:UML的六种箭头
泛化,多用于继承关系
**表示方法:**用实线空心三角箭头表示,如下图。
实现 (类与接口的关系)
**表示方法:**空心三角形箭头的虚线,实现类指向接口,如下:
依赖(临时性的delegation)
依赖性委托(Dependence):临时性的delegation,只是方法参数传递,而没有属性
表示方法: 虚线箭头,类A指向类B,即A依赖B,如下图:
关联(长期性)
程序中一个类的全局变量引用了另一个类,就表示关联了这个类,关联关系分为单项关联和双向关联。在Java中,单向关联表现为:类A当中使用了类B,其中B作为类A的成员变量。双向关联表现为:类A当中使用了类B作为成员变量;同时类B中也使用了类A作为成员变量。
**表示方法:**实线箭头,类A指向类B,即A关联B,如下图,消费者种关联了若干产品:
聚合
has-a关系
**表示方法:**尾部为空心菱形的实线箭头(也可以没箭头),车轮和大灯都属于汽车,如下图:
组合
表示方法: 尾部为实心菱形的实现箭头(也可以没箭头)
十一、可维护性!!(各种原则)
软件维护:修复错误,改善性能
四种维护:纠错性、适应性(更改环境,要能适应新版本环境)、完善性(对软件性能的增强)、预防性
模块化编程
高内聚,低耦合
分离关注点:多用委派
信息隐藏
评价模块化的几个标准
可分解性
可组合性
可理解性
可持续性——发生变化时收到影响范围最小
出现异常之后的保护——出现异常后受范围的影响小
模块化编程的几个原则
直接映射
尽可能少的接口——耦合度降低
尽可能小的接口——
显式接口
信息隐藏
SOLID原则
SRP 单一责任原则
不应该多于1个原因让你的ADT发生变化,否则就把他们拆开
OCP 开放-封闭原则——可以加新的类,但是不要改写好的类
对扩展的开放
模块应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
对修改的封闭
LSP Liskov替换原则
DIP 依赖转置原则——客户端应该依靠更加抽象的类
具体的模块依赖于抽象的模块
例子:
copy原来依赖于下面两个类,但是因为具体的类容易发生变化,如果具体的类发生变化,Copy也容易跟着动
在依赖翻转后,做一层接口层,进行隔离,面向接口进行编程
从稳定的依赖不稳定的,抽象的依赖具体的
变成抽象的依赖抽象的
ISP 接口隔离原则
大的接口可分解为多个小的接口
不同的接口向不同的客户端提供服务
客户端只方位你自己所需要的端口