写点什么

面向对象设计原则 -- 开放关闭原则 (OCP)

用户头像
张荣召
关注
发布于: 2020 年 09 月 27 日

面向对象分析设计(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)观察到这个通知后,链接无线网络。


改进目标四:使用观察者模式重新设计。



用户头像

张荣召

关注

还未添加个人签名 2018.05.02 加入

还未添加个人简介

评论

发布
暂无评论
面向对象设计原则--开放关闭原则(OCP)