设计原则 (understanding)
- 面向接口编程
- 依赖倒置
- 组合优于继承
- 单一职责
- 开放——封闭
- Liskov替换
面向接口编程(Program to An interface, Not An Implementation)
- 使用“接口继承”,而非“实现继承”
- 接口关注对象的概貌,将对象中“不变”的信息抽象出来,不涉及细节,因此是“稳定”的
Example:
面向接口的好处:
- Client只需关注如何进行业务活动(如:驾驶),而不必关心其使用对象的具体实现
- 一个对象可以很容易地被(实现了相同接口的)另一个对象所替换
- 对象间的连接不必硬绑定(hard wire)到一个具体类的对象上,因此增加了灵活性
- 松散耦合,增加了重用的可能性
依赖倒置(Dependency Inversion Principle)
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
- 依赖倒置原则使细节和具体实现都依赖于抽象,抽象的稳定性决定了系统的稳定性
- Example:
组合优于继承 (Favor Composition Over Inheritance )
- 继承:OO特点之一
- 组合:任务委托
- 二者:都是面向对象设计中用于重用的方法
- 开发早期,继承经常被过度使用
使用继承的问题:
组合优于继承-设计改进
- 优先使用组合可获得重用性与简单性更佳的设计
- 配合使用继承,扩充可用的组合类集,加大重用的范围
<% img /img/se_97.jpg %>
何时使用继承?
- 子类表达了“是一个父类的特殊类型”,而非“是一个由父类所扮演的角色”
- 子类的一个实例永远不需要转化为其它类的一个对象
- 子类是对其父类的职责进行扩展,而非重写或废除(LSP)
单一职责——Single responsibility Principle
- 所谓职责,可理解为功能,就是设计的类功能应该只有一个,而不是两个或更多
- 职责也可以理解为引起变化的原因:当一个类中有两个以上的变化方向,会产生过多的变化点
- Example:
开放封闭原则——Open For Extension, Yet Closed For Modification (OCP)
- OCP认为应该试图设计出永远也不需要改变的模块
- 可以添加新代码来扩展系统的行为;不能对已有的代码进行修改
OCP模块标准
- 模块的行为可以被扩展,以需要满足新的需求
- 模块的源代码是不允许进行改动的
- 一个软件系统的所有模块不可能都满足OCP,但是应该努力最小化这些不满足OCP的模块数量
- OCP法则是OO设计的真正核心
- 符合该法则便意味着最高等级的复用性和可维护性
- 一个OCP栗子:
- 改进
Liskov替换原则
- 使用指向基类(超类)的引用的函数,必须能够在不知道具体派生类(子类)对象类型的情况下使用它们 ==> 在任何父类出现的地方,都可以用子类替换(包括逻辑上和语义上)
- LSP是根据对象的“多态”而得出的
- 但在实现子类时必须要谨慎对待,以确保不会无意中违背了LSP
一个违背Liskov原则的栗子
- 对于下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//Rectangle类声明如下
public class Rectangle{
protected double width;
protected double height;
public Rectangle(){}
public void setWidth(double w){
this.width = w;
}
public void setHeight(double h){
this.height = h;
}
public double area(){
return width * height
}
}
//直接用Rectangle类, 输出200
Rectangle rect = new Rectangle();
rect.setWidth(10);
rect.setHeight(20);
System.out.println(rect.area());
//将Rectangle换成其子类Square,输出为400
Rectangle rect = new Square(0);
rect.setWidth(10);
rect.setHeight(20);
System.out.println(rect.area());
- 对于下面的代码:
LSP的保证
- LSP表明了ISA关系是与对象行为有关的
- 一个子类型不得具有比基更多的限制,这是因为可能对于基类型的某些使用是合法的,但是会因为违背子类型的其中一个额外限制,从而违背了LSP
- 一个简单的做法是不要将基类中子类不需要的函数暴露给子类
设计模式(understanding)
- Abstract Factory(抽象工厂)
- PROTOTYPE(原型模式)
- Singleton(单例模式)
- Adapter(适配器模式)
抽象工厂(Abstract Factory)
- 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
原型模式(Prototype)
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
- 缺点是每一个类都必须配备一个克隆方法
- 其实是将product和factory功能合二为一了
单例模式(Singleton)
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 解决的主要是性能问题,而非耦合(变化)的问题
适配器模式(Adapter)
- 把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作
- 适配类可以根据参数返还一个合适的实例给客户端
桥梁模式(Bridge)
- 将抽象部分与它的实现部分(行为)分离,使它们都可以独立地变化
- 解决2个方面的变化问题:抽象与实现(行为)。即一个类中多个方向的变化问题