本文主要是介绍SoftwareConstruction——Reusable,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. 可复用性的概念
programming for reuse 面向复用编程:开发出可复用的软件
programming with reuse 基于复用编程:利用已有的可复用软件搭建应用系统
优点
- 降低成本和开发时间
- 经过充分的测试,可靠、稳定
- 标准化,在不同应用中保持一致
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 objectpublic 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或构造方法中设定
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其方法来实现新的功能,客户端启动的的是第三方开发者派生的子类型。
3. 面向复用的设计模式
Adapter 适配器模式
将某个类/接口转换为client期望的其他形式
增加接口
- 通过增加一个接口,将已存在的子类封装起来
- client面向接口编程,从而隐藏了具体子类。
适用场合:你已经有了一个类,但其方法与目前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 {... //reppublic ArrayStack() {...}public void push(Item e) {...}public Item pop() { ... }
}
//装饰器类,可以是一个抽象类,用于扩展出有各个特性方面的各个子类
public abstract class StackDecorator implements Stack {protected final Stack stack; //用来保存delegation关系的reppublic 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 外观模式
- 客户端需要通过一个简化的接口来访问复杂系统内的功能
- 提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统做了一个封装,简化客户端使用
- 便于客户端学习使用,解耦
Strategy 策略模式
- 有多种不同的算法来实现同一个任务
- 但需要client根据需要动态切换算法,而不是写死在代码里
- 为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例
Template Method 模板方法模式
- 框架:白盒框架
- 做事情的步骤一样,但具体方法不同
- 共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现
- 使用继承和重写实现模板模式
适用场合:有共性的算法流程,但算法各步骤有不同的实现典型的“将共性提升至超类型,将个性保留在子类型”
Iterator 迭代器模式
客户端希望遍历被放入容器/集合类的一组ADT对象,无需关心容器的具体类型
也就是说,不管对象被放进哪里,都应该提供同样的遍历方式
实现方式是在ADT类中实现Iterable接口,该接口内部只有一个返回一个迭代器的方法,然后创建一个迭代器类实现Iterator接口,实现hasnext()、next()、remove()这三个方法。
这篇关于SoftwareConstruction——Reusable的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!