一.设计可复用的类
需要的知识:
代码的封闭性与信息隐藏
继承与重写
参数多态与重载
泛型编程
上面的第三章已经介绍过了,这一节介绍如下内容:
行为子类型与Liskov替换原则(LSP)
组合与委托
(1)行为子类型与Liskov替换原则(LSP)
子类型多态:客户端可用统一的方式处理不同类型的对象
LSP:静态检查的标准:
子类型可以增加方法,但不可删
子类型需要实现抽象类型中的所有未实现方法
子类型中重写的方法必须有相同或子类型的返回值
子类型中重写的方法必须使用同样类型的参数
子类型中重写的方法不能抛出额外的异常
LSP:子类型方法有更具体的行为:
更强的不变量
更弱的前置条件
更强的后置条件
Example 1:
子类满足相同的不变量(自身随意增加不变量)
重写的方法满足相同的前置条件和后置条件
故满足LSP
总结LSP(强行自类型化)需要满足的限制条件为:
1. 前置条件不能强化
2. 后置条件不能弱化
3. 不变量要保持(或增强)
4. 子类型方法参数:逆变
5. 子类型方法的返回值:协变
6. 子类型不能抛出新的异常,除了异常类型:协变
介绍协变:
父类型——子类型:越来越具体specific返回值类型:不变或变得更具体(即后置条件变强)
异常的类型:也是如此。
介绍反协变、逆变:
父类型——子类型:越来越具体specific
参数类型:要相反的变化,要不变或越来越抽象(即前置条件变弱)
但是反协变、逆变在java中的重写静态类型检查中是被不被允许的,除非它是重载
介绍数组的协变:
基于Java的自类型化规则,父类数组 T[] 可以包含T 或T的子类
下面报错的原因是myNumber指向的还是一个Integer[] 而不是Number[]
二.设计系统级复用的 libraries 与frameworks
libraries 与frameworks的不同之处:
Library: 一组类和方法提供可复用的功能。
Frameworks:可复用的类似骨架的代码,能够别定制以适应不同的应用,并且它回调客户端的代码。
之所以library 和framework 被称为系统层面的复用,是因为它们不仅定义了1 个可复用的接口/ 类,而是将某个完整系统中的所有可复用的接口/ 类都实现出来,并且定义了这些类之间的交互关系、调用关系,从而形成了系统整体的“架构”
术语介绍:
API: Application Programming Interface, 一个library或 framework的接口
Client: 客户端:使用API的代码
Plugin: 插件:定制框架方法功能的客户端代码
Extension point:框架内预留的“空白”,开发者开发出符合接口要求的代码( 即plugin) , 框架可调用,从而相当于开发者扩展了框架的功能
Protocol:协议:API与客户端之间的相互作用
Callback: 回调:框架将要调用适应客户端需求的插件
Lifecycle method: 依据协议与插件的状态,回调的方法会按照一定的顺序
(1) API 的设计
为什么说API的设计很重要?
API 是程序员最重要的资产和“荣耀”,吸引外部用户,提高声誉。
一个好的应用编程接口(API)应该具备的特点:
1. 容易学
2. 容易使用,甚至不需要文档
3. 难以误用
4. 容易读,原来的代码不需要改变
5. 完全能够满足要求
6. 易于应对新的变化
7. 对使用者合适
①API 应该集中将一件事情完成好
功能需要向客户解释清楚
②API 应该尽可能的小规模
1. 要满足客户的所有需求
2. 可以添加功能,但不要修改原来的代码
3. 概念的说明会占大部分
③实现过于细节影响 API的功能
1. 实现过于细节对API有害
2. 可能会造成信息泄露
3. 尽可能的实现信息隐藏
④文档说明很重要
1. 每个类,方法,接口,构造器,参数,异常都要有注释说明
2. 方法的前置条件,后置条件,与其他的效果
3. 标注线程的安全性
⑤考虑执行的结果
1. 不好的决策会限制执行
2. 不要忽略API而选择直接执行
3. 好的设计有好的执行效果
4. 坏的API决策对执行效果影响很真实而且一直很真实
⑥API要与运行平台很好的匹配
1. 使API常规化
2. 利用对API有利的特性
3. 了解避免API的陷阱错误
4. 不要直译API
⑦类的设计
1. 变量尽可能的做到不变
2. 子类要满足LSP原则