面向对象设计原则 -- 开放关闭原则 (OCP)
面向对象分析设计(OOA/OOD)案例分析:
UML 练习:设计一个控制电话拨号的软件。
Use Case:拨打电话
我们按下数字按钮,屏幕上显示号码,扬声器发出按键音。
我们按下 send 按钮,系统接通无线网络,同时屏幕上显示正在拨号。
1.一级直接抽象:(客观世界:问题领域======>对象)静态分析。
设计一个控制电话拨号的软件(拨号盘)。
我们按下数字按钮,屏幕上显示号码,扬声器发出按键音。
我们按下 send 按钮,系统接通无线网络,同时屏幕上显示正在拨号
设计评估:好?不好?评估依据是什么?
评估方法:单从类图上看不出设计的好坏。把类图放到开发上下文环境中-------不断的需求变更
(开发期间需求变更,上线之后需求变更),需求变更后,设计是否能有效支撑
--------衡量设计好不好的依据
根据 UML 编码,并评估
public class Button{ //1.具体类:抽象层次比较低。针对具体类编码,降低可扩展性。(比如,需求变更,出现其他字符按钮。)
//------针对接口编程,而非针对具体类编程
public final static int SEND_BUTTON=-99;
private Dialer dialler; //2.强依赖 Dialler 具体类,对 Dialler 的耦合程度偏高。(违反高内聚,低耦合设计原则)
//Button 的移植他处时,必须携带 Dialler。dialler 如果再对其他对象产生强依赖。则移植性更弱。
private Integer token;
public Button(Dialler dialler,Integer token){
this.dialer=dialer;
this.token=token;
}
public void press(){
swith(token){ //3.需求变更:“*”按钮,“#”按钮,要操作需求时,必须到这里来修改代码。
//修改这里的代码,可能会影响其他代码。
//-------违反 OCP 原则(open-close priciple)
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9:
dialler.enterDigit(token);
break;
case SEND_BUTTON:
dialler.dial();
break;
default:
throw new UnspportedOperationException("unkown button pressed:token="+token);
}
}
}
public class Dialler{
public void enterDigit(int digit){
screen.display(digit);
speeker.beep(digit);
}
public void dial(){
screen.display("dialing....");
radio.connect();
}
}
面向对象设计的目的和原则:
OOD 目的:强内聚,低耦合,从而使系统
易扩展 ---易于增加新的功能
更健壮 ---不容易被粗心的程序元破坏
可移植 ---能够在多样的环境下运行
更简单 ---容易理解,容易维护
OOD 原则:
OCP(Open/Closed Principle:开闭原则)
对扩展开放 (open for extension)
对修改关闭 (closed for modification)
简言之:不需要修改软件实体(类,模块,函数等),就能实现功能的扩展
扩展模块:传统方式修改源代码。如何不修改源码而能实现功能扩展?=====>关键在于抽象。
针对电话拨号软件改进:
改进目标一:实现 Button 可扩展(*,#等)
改进评估:实现了 Button 的可扩展。但是 Button 子类对象对 Dialler 强耦合,如果 Dialler 做了修改,Button 子类也会被迫做出修改。
产生问题:如果降低对 Dialler 的耦合?
改进目标二:对 Dialler 低耦合。
分析:数字按钮======>dialler 做的事情:screen 显示,speeker 出音。
Send 按钮======>dialler 做的事情:连接无线网络,screen 显示拨号。
推论:不同按钮,dialler 做不同的事情====>可将 dialler 要做的事情,视为不同的策略====>使用策略模式对 dialler 进行扩展。
改进方案:策略模式
改进效果:Button 和策略 ButtonStrategy 抽象层面,保持稳定。Button 直接依赖策略,不直接依赖 dialler。通过策略委托给 dialler。
改进效果的评估:(使用需求变更进行评估),假设 Button 需要扩展“* 按钮",或者"#按钮”,ButtonStrategy 可以保持稳定不变,但是 dialler 需要扩展*,#的 操作,即需要修改 switch case 语句。====>dialler 不具有扩展性。
改进目标三:提高 dialler 的可扩展性。
改进分析:dialler 拨号盘已经实现操作:1.数字按钮操作(screen,speeker),2,连接无线电网络(radio)。为了保持 dialler 的稳定性,即使有需求变更,不再对 dialler 进行修改。数字按钮操作==>数字策略适配器;send 按钮操作===>send 策略适配器;XX 扩展按钮操作===>XX 策略适配器===>适配到目标对象(不一定是 dialler)。
改进效果:dialler 拨号盘只负责数字键和 send 键的操作,由 StrategyAdpter 适配 dialler 的操作;如果有其他需求需要扩展,可使用相应的适配器适配到目标对象的目标操作。
改进效果的评估: 满足 Button 的可扩展,Button 和 ButtonStrategy 策略之间关系稳定,即使有需求变更,Button 和 ButtonStrategy 的代码,也可保持稳定,不需要修改。
到此,遵循 OOD 设计原则(OCP),使用策略+适配模式,达到了设计目标。
抛开现在的设计成果,返回原始需求重新审视:
按下数字按钮(subject), screen(observer)观察到这个通知后,更新屏显,
speeker(observer)观察到这个通知后,更新语音提示;
按下 send 按钮(subject),screen(observer)观察到这个通知后,更新屏幕显示拨号,
speeker(observer)观察到这个通知后,更新语音提示,
radio(observer)观察到这个通知后,链接无线网络。
改进目标四:使用观察者模式重新设计。
评论