当前位置:网站首页>【HIT-SC-MEMO4】哈工大2022软件构造 复习笔记4
【HIT-SC-MEMO4】哈工大2022软件构造 复习笔记4
2022-08-04 05:32:00 【XMeow】
四、可复用性
4.1 可复用性的概念
programming for reuse 面向复用编程:开发出可复用的软件 programming with reuse
基于复用编程:利用已有的可复用软件搭建应用系统
- 优点
- 降低成本和开发时间
- 经过充分的测试,可靠、稳定
- 标准化,在不同应用中保持一致
4.2 面向复用的软件构造技术
Liskov Substitution Principle 里氏替换原则(LSP)
- 子类型多态:客户端可用统一的方式处理不同类型的对象
- 在可以使用父类的场景,都可以用子类型代替而不会有任何问题
编译强制规则
- 子类型可以增加方法,但不可删除方法
- 子类型需要实现抽象类型中的所有未实现方法
- 协变:子类型中重写的方法必须有相同或子类型的返回值或者符合co-variance的参数
- 逆变:子类型中重写的方法必须使用同样类型的参数或者符合contra-variance的参数
- 子类型中重写的方法不能抛出额外的异常
Also applies to specified behavior (methods):
- Same or stronger invariants 更强的不变量
- Same or weaker preconditions 更弱的前置条件
- Same or stronger postconditions 更强的后置条件
协变 & 反协变
父类型 → 子类型:
协变:返回值和异常不变或越来越具体
逆变(反协变):参数类型要相反地变化,要不变或越来越抽象
泛型
泛型类型是不支持协变的:
如
ArrayList<String>
是List<String>
的子类型,但List<String>
不是List<Object>
的子类型
这是因为发生了类型擦除,运行时就不存在泛型了,所有的泛型都被替换为具体的类型。
但是在实际使用的过程中是存在能够处理不同的类型的泛型的需求的,如定义一个方法参数是List<E>
类型的,但是要适应不同的类型的E
,于是可使用通配符?
来解决这个需求:
- 无类型条件限制:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
}
- 当为A类型的父类型
public static void printList(List<? super A> list){
...}
- 当为A类型的子类型
public static void printList(List<? extends A> list){
...}
委派(Delegation)
一个对象请求另一个对象的功能
通过运行时动态绑定,实现对其他类中代码的动态复用
“委托”发生在object层面
“继承”发生在class层面
Types of Delegation:
- 依赖 Dependency:临时性的delegation
- 把被delegation的对象以参数方式传入。只有在需要的时候才建立与被委派类的联系,而当方法结束的时候这种关系也就随之断开了。
class Duck {
//no field to keep Flyable object
public void fly(Flyable f) {
f.fly(); } //让这个鸭子以f的方式飞
public void quack(Quackable q) {
q.quack() }; //让鸭子以q的方式叫
}
- 关联 Association:永久性的delegation
- 分为:组合(Composition)和聚合(Aggregation)
- 被
delegation
的对象保存在rep
中,该对象的类型被永久的与此ADT绑定在了一起。
- 组合 Composition:更强的Association,但难以变化
- 在
rep
或construction
中设定
- 在
Duck d = new Duck();
d.fly();
class Duck {
//这两种实现方式的效果是相同的
Flyable f = new FlyWithWings(); //写死在rep中
public Duck() {
f = new FlyWithWings(); } //写死在构造方法中
public void fly(){
f.fly(); }
}
- 聚合 Aggregation:更弱的Association,可动态变化
- 在构造方法中传入参数绑定
Flyable f = new FlyWithWings();
Duck d = new Duck(f);
d.fly();
class Duck {
Flyable f; // 这个必须由构造方法传入参数绑定
public Duck(Flyable f) {
this.f = f; } // 在此传入
public void fly(){
f.fly(); }
}
组合(Composition)和CRP原则
利用delegation的机制,将功能的具体实现与调用分离,在实现中又通过接口的继承树实现功能的不同实现方法,而在调用类中只需要创建具体的子类型然后调用即可。组合就是多个不同方面的delegation的结合。
抽象层是不会轻易发生变化的,会发生变化的只有底层的具体的子类型,而具体功能的变化(实现不同的功能)也是在最底层,所以抽象层是稳定的。而在具体层,两个子类之间的委派关系就有可能是稳定的也有可能是动态的,这取决于需求和设计者的设计决策。
白盒框架 & 黑盒框架的原理与实现
- 黑盒框架
- 通过实现特定接口进行框架扩展,采用的是delegation机制达到这种目的,通常采用的设计模式是策略模式(Strategy)和观察者模式(Observer);
- 黑盒所预留的是一个接口,在框架中只调用接口中的方法,而接口中方法的实现就依据派生出的子类型的不同而不同,它的客户端启动的就是框架本身。
- 白盒框架
- 通过继承和重写实现功能的扩展,通常的设计模式是模板模式(Template Method);
- 白盒框架所执行的是框架所写好的代码,只有通过override其方法来实现新的功能,客户端启动的的是第三方开发者派生的子类型。
4.3 面向复用的设计模式
Adapter 适配器模式
- 将某个类/接口转换为client期望的其他形式
- 增加接口
- 通过增加一个
interface
,将已存在的子类封装起来 - client面向
interface
编程,从而隐藏了具体子类。
- 通过增加一个
适用场合:你已经有了一个类,但其方法与目前client的需求不一致。
根据OCP原则,不能改这个类,所以扩展一个adaptor和一个统一接口。
Decorator 装饰者模式
继承组合会引起组合爆炸/代码重复
- 为对象增加不同侧面的特性
- 对每一个特性构造子类,通过委派机制增加到对象上
- 客户端需要一个具有多种特性的object,通过逐层的装饰来实现
例子:
Stack
对应上图Component
接口ArrayStack
对应ConcreteComponent
,基础类StackDecorator
对应Decorator
,装饰类(可以是抽象类)UndoStack
对应ConcreteDecoratorA
,装饰类的具体类
//Stack接口,定义了所有的Stack共性的基础的功能
interface Stack {
void push(Item e);
Item pop();
}
//最基础的类,啥个性也没有的Stack,只有共性的实现
public class ArrayStack implements Stack {
... //rep
public ArrayStack() {
...}
public void push(Item e) {
...}
public Item pop() {
... }
}
//装饰器类,可以是一个抽象类,用于扩展出有各个特性方面的各个子类
public abstract class StackDecorator implements Stack {
protected final Stack stack; //用来保存delegation关系的rep
public StackDecorator(Stack stack) {
this.stack = stack; //建立稳定的delegation关系
}
public void push(Item e) {
stack.push(e); //通过delegation完成任务
}
public Item pop() {
return stack.pop(); //通过delegation完成任务
}
}
//一个有撤销特性功能的子类
public class UndoStack extends StackDecorator implements Stack {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) {
super(stack); //调用父类的构造方法建立delegation关系
}
public void push(Item e) {
log.append(UndoLog.PUSH, e); //实现个性化的功能
super.push(e); //共性的功能通过调用父类的实现来完成
}
public void undo() {
//implement decorator behaviors on stack
}
...
}
- 使用装饰对象:层层嵌套初始化
new Class1(new Class2(new Class3(...)))
// 先创建出一个基础类对象
Stack s = new ArrayStack();
// 利用UndoStack中继承到的自己到自己的委派建立起从UndoStack到ArrayStack的delegation关系
// 这样,UndoStack也就能够实现最基础的功能,并且自身也实现了个性化的功能
Stack us = new UndoStack(s);
// 通过一层层的装饰实现各个维度的不同功能
Stack ss = new SecureStack(new SynchronizedStack(us));
JDK中装饰器模式的应用:
static List<T> unmodifiableList(List<T> list)
static Set<T> synchronizedSet(Set<T> set)
facade 外观模式
- 客户端需要通过一个简化的
interface
来访问复杂系统内的功能 - 提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统做了一个封装,简化客户端使用
- 便于客户端学习使用,解耦
Strategy 策略模式
- 有多种不同的算法来实现同一个任务
- 但需要client根据需要动态切换算法,而不是写死在代码里
- 为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例
Template Method 模板方法模式
- 框架:白盒框架
- 做事情的步骤一样,但具体方法不同
- 共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现
- 使用继承和重写实现模板模式
适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”
Iterator 迭代器模式
- 客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型
- 也就是说,不管对象被放进哪里,都应该提供同样的遍历方式
实现方式是在ADT类中实现Iterable
接口,该接口内部只有一个返回一个迭代器的方法,然后创建一个迭代器类实现Iterator
接口,实现hasnext()
、next()
、remove()
这三个方法。
边栏推荐
猜你喜欢
LeetCode_Nov_3rd_Week
LeetCode_Dec_1st_Week
【五一专属】阿里云ECS大测评#五一专属|向所有热爱分享的“技术劳动者”致敬#
Design and implementation of legal aid platform based on asp.net (with project link)
库函数的模拟实现-C语言
arm-3-中断体系结构
LeetCode_Dec_2nd_Week
AWS uses EC2 to reduce the training cost of DeepRacer: DeepRacer-for-cloud practical operation
Detailed steps to install MySQL
SSO单点登陆
随机推荐
LeetCode_Dec_2nd_Week
Deep Learning Theory - Overfitting, Underfitting, Regularization, Optimizers
C语言对文件的操作(完整版)
[开发杂项][调试]debug into kernel
[开发杂项][VS Code]remote-ssd retry failed
【独立游戏体验计划】学习记录
AWS uses EC2 to reduce the training cost of DeepRacer: DeepRacer-for-cloud practical operation
[English learning][sentence] good sentence
The usefulness of bind() system call
Completely remove MySQL tutorial
淘宝分布式文件系统存储引擎(一)
集合--LinkedList
【c语言】整数的二进制表现形式是什么?
【C语言】数组名是什么
第三章 标准单元库(下)
[日常办公][shell]常用代码段
C语言数组的深度分析
strlen 转义字符
库函数的模拟实现-C语言
2022在 Go (Golang) 中使用微服务的系统课程