软件体系结构——第七章<面向对象的设计原则>
一、设计需要原则
面向对象的设计原则是构造高质量软件的出发点
什么是好的设计?
-
容易理解
-
容易修改和扩展
-
容易复用
-
容易实现与应用
-
简单、紧凑、经济适用
-
让人工作起来心情愉快的设计
面向对象的基本设计原则(模式):
-
LSP:Liskov替换原则 The Liskov Substitution Principle
-
OCP:开放-封闭原则 The Open-Close Principle
-
SRP:单一职责原则 The Single Responsibility Principle
-
ISP:接口隔离原则 The Interface Segregation Principle
-
DIP:依赖倒置原则 The Dependency Inversion Principle
二、LSP-Liskov替换原则
LSP(The Liskov Substitution Principle)
定义:“若对于类S的任一对象o1,均有类T的对象o2存在,使得在类T定义的所有程序P中,用o1替换o2之后,程序的行为不变,则类S是类T的子类”,即“子类对象必须可以替换父类对象”
违背LSP原则的范例——
怎么解决此问题?——使用抽象
抽象类与具体类的区别:
正确方案:
三、OCP-开放-封闭原则
lOCP(The Open-Close Principle)的定义
- 对任何一个事物来说,“变化是永恒的主题,不变是相对的定义”,由此,可得出软件实体(类、模块、函数等)应该是可扩展的,但是不可修改的——功能不变性
OCP原则的特征:
-
对于扩展是开放的(Open for extension):模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求
-
对于更改是封闭的(Closed for modification):对模块行为扩展时,不需改动模块源代码即可满足新需求
OCP的关键在于扩展抽象的利用:
-
抽象技术:abstract class、Interface
-
抽象内容预见了可能的所有扩展(闭)
-
由抽象可以随时导出新的类(开)
实例:手与门
手与门的行为表现为:
-
开门(open)
-
关门(close)
-
判断门的状态(isOpened)
public class Door {
private boolean _isOpen=false;
public boolean isOpen(){
return _isOpen;
}
public void open(){
_isOpen = true;
}
public void close(){
_isOpen = false;
}
}
public class Hand {
public Door door;
void do() {
if (door.isOpen())
door.close();
else
door.open();
}
}
//Test
public class SmartTest {
public static void main(String[] args) {
Hand myHand = new Hand();
myHand.door = new Door();
myHand.do();
}
}
新的需求出现后如何应对?如:用手开关抽屉
public class Hand {
public Door door;
public Drawer drawer;
void do(int item) {
switch (item){
case 1: //开门程序
if (door.isOpen())
door.close();
else door.open();
break;
case 2: //开抽屉程序
if (drawer.isOpen())
drawer.close();
else drawer.open();
break;
}
}
}
//Test
public class SmartTest {
public static void main(String[] args) {
Hand myHand = new Hand();
myHand.door = new Door();
myHand.drawer = new Drawer();
myHand.do(1); //手开门
}
}
发现,这样改动非常麻烦!
我们设计出符合OCP的设计方案:
public interface Excutable {
public boolean isOpen();
public void open();
public void close();
}
public class Hand {
public Excutable item;
void do() {
if (item.isOpen())
item.close();
else
item.open();
}
}
public class Door implements Excutable {
private boolean _isOpen = false;
public boolean isOpen() {
return _isOpen;
}
public void open() {
_isOpen = true;
}
public void close() {
_isOpen = false;
}
}
public class Drawer implements Excutable {
private boolean _isOpen = false;
public boolean isOpen() {
return _isOpen;
}
public void open() {
_isOpen = true;
}
public void close() {
_isOpen = false;
}
}
//add a refrigerator
public class Refrigerator implements Excutable {
private boolean _isOpen = false;
public boolean isOpen() {
return _isOpen;
}
public void open() {
_isOpen = true;
}
public void close() {
_isOpen = false;
}
}
//Test
public class SmartTest {
public static void main(String[] args) {
Hand myHand = new Hand();
myHand.item = new Door();
myHand.do();
}
}
通过扩展应对新需求,不需要修改任何原有的设计和代码~
OCP的优势:
-
可以极大提高软件的设计质量,耦合度低、易扩展、易修改
-
当预测到可能的变化时,通过抽象类(接口)来隔离它。
-
关键技术是“扩展抽象”原则的利用。
-
成功的预测将极大提高软件的生存力,而失败的预测,将带来“不必要的复杂性”的设计“臭味”
四、SRP-单一职责原则
作为面向对象系统最基本的元素,类自身的设计质量将直接影响到整个设计方案的质量。因此,对于单个类而言,最核心的工作就是类的职责分配过程——职责单一。
SRP(The Single Responsibility Principle)定义:就一个类而言,应该仅有一个引起它变化的原因,即内聚性——一个类只完成一个功能
违反SRP的案例:
解决方案:
SRP是一个非常简单的原则,但却是最难正确应用的原则之一——类职责的高内聚
- 如模块独立性原则“高内聚、低耦合”,知道该原则是追求的设计目标,但难以描述怎样才能达到高内聚的目标
SRP明确告诉设计人员必须保持类职责的内聚性
- 主要做法:结合业务场景考虑职责的相关性,与系统的耦合程度密切关联起来
五、ISP-接口隔离原则
ISP(The Interface Segregation Principle)定义
-
客户不应该依赖他们用不到的方法,只给每个客户提供所需的接口——角色
-
为了避免“肥接口(没有关系的接口合并在一起)(fat interface)”,应当以一个类实现多个专用的接口,而各客户仅仅获知必须的接口
优势:
-
为不同角色提供宽窄不一的接口,以对付不同的客户端或使用者(都是业务参与者);
-
接口的职责明确,有利于系统的维护,同时,有利于降低设计成本;
-
平常设计时总是向客户端和使用者提供public接口是一种承诺,应尽量减少这种承诺;
-
接口污染:为节省接口的数目,将类似的接口合并,使得接口变得臃肿,造成接口污染。
接口污染案例:
解决方案:
-
使用委托分离接口: 适配器(Adapter)模式
-
使用多重继承(实现)分离接口
六、DIP-依赖倒置原则
传统的层次结构:
DIP特性:
-
高层模块不依赖于低层模块,二者都依赖于抽象(需要定义中间抽象层)
-
抽象不依赖于细节,细节依赖于抽象
-
针对接口编程,不要针对实现(细节)编程
符合DIP的系统:
启发式原则:
-
“依赖于抽象”—程序中所有依赖关系都应该终止于抽象类或者接口
-
启发式原则——要符合LSP原则
- 任何类都不应该从具体类派生(始于抽象、来自具体)
- 任何方法都不应该改写其任何基类中已经实现的方法(除非有特殊的需求)
-
UML中箭头的方向就代表了依赖的方向
本质:
-
通过抽象提取业务本质,并建立一个稳定的静态结构(抽象类图)描述这个业务本质
-
对于具体的业务规则的处理是在这个业务本质的基础上的扩展
-
注意:技术、工具、意识形态等的发展可能使业务规则不断变化,但业务的本质不变;因此,DIP原则可帮助设计人员轻松的适应这些变更