软件构造笔记整理三
- LSP替换原则
使得父类型可以安全的替换为子类型
实现子类型多态:客户端可用统一的方式处理不同类型的对象
子类重写父类的方法应该满足的条件:
增加父类的方法 | 减少父类的方法 | 方法入口参数 | 方法返回值 | 方法异常 |
---|---|---|---|---|
可 | 不可以 | 相同或父类型(逆变) | 相同或子类型(协变) | 相同或子类型(协变) |
协变:关于返回值的类型,应该保持不变或者变得更具体,也就是与派生的方向一致。
逆变:关于参数的类型,应该保持不变或者变得更抽象,也就是与派生的方向相反。
我的理解是:子类方法的参数范围要比父类的大,返回值类型的范围要比父类的小。
另外,LSP还允许:
不变量 | 不变量 | 后置条件 |
---|---|---|
更强 | 更弱 | 更强 |
注意:方法参数逆变时,编译器将override视为overload
- 泛型与泛型中的LSP原则
相关的有泛型接口、泛型类、泛型方法
若使用泛型参数时不指定具体类型,就会被视为object
注意1:泛型方法可以在普通类中,返回值前定义泛型参数即可。
注意2:使用泛型的方法不一定是泛型方法,还可以是泛型方法中的普通成员方法,只是刚好用到泛型类的泛型参数,依赖于泛型类的定义。
注意3:调用泛型方法不需要手动指定具体类型(编译器根据调用自动推导),但是类型参数只能是引用类型 。
注:4:静态方法、静态属性不能访问泛型类上定义的泛型参数。
- 泛型中的LSP原则
泛型类型是不支持协变的。
例如:ArrayList 是List的子类型,但List不是List的子类型。这是因为发生了类型擦除,运行时就不存在泛型了,所有的泛型都被替换为具体的类型。
定义一个方法参数是List类型的,可使用通配符?来适应不同的类型的E。这样,传参时就支持协变了。
- 委派
一个对象请求另一个对象的功能。
主要分为两类:
Dependency:依赖关系,临时性的delegation。把被delegation的对象以参数方式传入。只有在需要的时候才建立与被委派类的联系,而当方法结束的时候这种关系也就随之断开了,比较松散。
Association:关联关系,永久性的delegation。被delegation的对象保存在rep中,该对象的类型被永久的与此ADT绑定在了一起。
- Aggregation: 更弱的association,可动态变化,在构造方法中传入参数绑定,比如人与车的关系。
- Composition: 更强的association,但难以变化,在rep或构造方法中直接写死,比如人与四肢的关系。
- 组合 与 Composite Reuse Principle(CRP)
可以理解为多个不同方面的delegation的结合
利用delegation的机制,将功能的具体实现与调用分离。
在调用类中只需要创建具体的子类型然后调用,通过接口的具体类给出功能的不同实现方法。
特点:
- 抽象层稳定
抽象层是不会轻易发生变化的,会发生变化的只有底层的具体的子类型,而具体功能的变化(实现不同的功能)也是在最底层,所以抽象层是稳定的。
- 具体层变化
而在具体层,两个子类之间的委派关系就有可能是稳定的也有可能是动态的,这取决于需求和设计者的设计决策。
注:委托发生在object层面,而继承发生在class层面。