4-2 面向复用的软件构造技术
1、设计可复用的类
1.1 行为子类型和LSP
子类型:
相比于父类型,要有相同或更强的ADT(前置条件变弱或后置条件变强、更强的不变量);
在java中表现为:子类型可以增加方法;子类型中重写的方法必须返回相同或子类型的返回值(协变);子类型中重写的方法必须接受相同类型的参数(逆变);子类型重写的方法不能抛出额外异常。
例子:
假如一个长方形类、一个正方形类,然后正方形类继承长方形类。长方形有一个方法setWidth:改变某条边的长度。这时正方形类就无法继承这个方法,因为会破坏规约。正方形类和长方形类不应该是继承,而应该是委托的关系。
LSP:
协变:
子类型中重写的方法的返回值是父类中方法返回值的子类,子类的异常也应该是父类异常的子类。这种同向的变化就叫协变。
协变还有一层含义就是子类型的实例可以被赋给父类型:
逆变:
子类型中重写的方法的参数是父类中方法参数的父类。这种逆向的变化就叫逆变。java中不支持逆变,当作重载处理(Overload)。
数组是协变的。泛型不协变,泛型只存在于编译阶段,之后会被擦除,例如:List和List是相同的,类型擦除后都是List。
泛型擦除:
对于泛型使用类型查询:
instance of一个泛型类会编译不通过,getclass可运行,但运行时类型被擦除,故Pair<String>和Pair<Employee>是相等的。
泛型不存在协变,故不可以将一个泛型类赋值给另一个泛型类,这里的List<a>与List<b>毫无关系,故不可以这样赋值:
要写泛型方法可使用通配符:
下限通配符、上限通配符, 有了通配符,泛型就有子类的概念了:
PECS:producer要用super,consumer要用extend。
1.2、委托、组合
如果一个类继承另一个类,很可能继承下来很多不可用的方法,需要重写这些方法为空。好的做法:委托。可以避免大量不需要的方法。
ADT的比较的两种实现:
1、实现一个比较器,然后将比较任务委托给这个比较器Comparator;
注意comparator中compare函数的逻辑:返回1的时候将两个对象互换,所以下面这个是升序:
2、这个ADT实现Comparable接口,在ADT中实现compareTo函数,这样就把比较封装在了ADT内部,而不用额外实现一个比较器类,但这不是委托。
显式委托:通过传入对象,然后调用这个对象的方法,比如说比较器:
隐式委托:类中声明一个成员变量,然后类的方法调用这个成员变量的方法:
B隐式委托A,就是指B中声明一个A类型的成员变量a,然后B中方法的实现都依托于这个a。例如lab2中:
组合:问题在于对象层面而不在于类的层面,一个类的不同对象可能有不同的行为。
比如说开发一个动物类ADT,行为有叫和飞,有十余种飞和叫的方式,好的实现:接口之间通过extends实现拓展,比如说鸭子会叫又会飞,就可以extends Flyable、Quackable接口,其中Flyable、Quackable接口又各有十余种实现:
然后鸭子类对fly、quack的实现可以委托给Flyable和Quackable两个接口:
客户端给一个对象分配它的行为方式,这样同样是duck类,就可以有不同的行为方式:
组合的总体形式:
委托的种类:
(1)依赖Dependency
这是临时性的委托,通过方法传参建立局部的联系,被委托的对象也不是这个类的成员变量:
(2)association
这是永久性的委托,一个类将要委托的对象作为自己的成员变量,其有两种形态,分别是composition和aggregation。
(3.1)组合composition
这是更强的association,但难以变化,就是初始化的时候对成员变量进行赋值,是死的,没办法变化。
(3.2)聚合aggregation
这是更弱的association,可以动态变化,即有专门的方法对其成员变量进行赋值。
2、系统层面的可复用——库和框架
库的复用:我们写的代码调用库;
框架的复用:我们写的代码填充框架,框架调用我们写的代码。
白盒框架:
框架中有一些未完成的方法、空白,子类通过继承和重写方法完成对框架的填充、子类有主方法但调用是由框架调用。例如模板模式就是白盒框架。客户端写main。一次只能进行一次扩展,开发者框架。
一个抽象类白盒框架:
黑盒框架:
看不到代码,框架只提供一系列接口,通过plugin实现接口来实现框架的填充,相当于是通过委托来实现,由框架调用。框架写main。一次可进行多次扩展,用户框架。
这个黑盒框架有一个成员变量作为接口,外部plugin实现接口:
两个框架的工作流程:
设计框架的指导原则:
1、确定该框架的领域;
2、找出通用的部分,在框架中实现;
3、可变的部分留给外部去实现;
4、接口——抽象类——具体类:由抽象到具体,通用的向上放。
使用库和框架的投资回报曲线:
Collections介绍:
主要就是有四个接口:list、set、map、queue,实战都接触了,不记了。
迭代器的删除是安全的,其他对集合的删除会造成索引的混乱。
迭代器在4-3有详细介绍。