`
helloyesyes
  • 浏览: 1273349 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

软件设计原则----开-闭原则(OCP)

阅读更多

 

软件实体(类、模块、函数等)应该是可以扩展的,同时还可以是不必修改的,更确切的说,函数实体应该:
(1)对扩展是开放的
当应用的需求变化时,我们可以对模块进行扩展,使其具有满足改变的新的行为。即:我们可以改变模块的功能
(2)对更改是封闭的
对模块进行扩展时,不必改动模块已有的源代码或二进制代码。

 

分析:

  • 世界是变化的(而且变化很快),软件是对现实的抽象。---->软件必须能够扩展
  • 如果任何修改都需要改变已经存在的代码,那么可能导致牵一发动全身现象,进而导致雪崩效应,使软件质量显著下降。

实现OCP的关键是抽象:

例1:既不开放也不封闭的Client:

 

问题:
client和server都是具体类,接口与实现没有实现分离。如果我们想要让client调用一个新的server类,那么我们不得不修改client的源代码。从而带来编译、链接、部署等一系列的问题。

  1. class client{
  2. server& s;
  3. public :
  4. client(server& SER):s(SER) {}
  5. void useServer(){
  6. s.ServerFunc();
  7. }
  8. };

  1. class server{
  2. int serverData;
  3. public :
  4. void ServerFunc();
  5. };

修改后的设计:

  • 设计中ClientInterfece类是一个拥有抽象成员函数的抽象类。Client类使用一个抽象类,然而Client的对象却是用Server类的派生类的对象。
  • 如果希望Client对象使用一个不同的服务器类,那么只需从ClientInterfece类派生一个新的类,无需对Client类做任何改动。
  1. class client{
  2. ClientInterface& ci;
  3. public :
  4. client(ClientInterface &
  5. CI):ci(CI){}
  6. void useServer(){
  7. ci.ServerFunc();
  8. }
  9. };

  1. class ClientInterface{
  2. virtual void ServerFunc()=0;
  3. };
  4. class server: public ClientInterface{
  5. int serverData;
  6. public :
  7. void ServerFunc();
  8. };

问题:
为什么上述的ClientInterface这个类要取这么个名字,而不叫AbastractServer?
其实这里面蕴含了一个思想:

——client类中更多的描述了高层的策略,而Server类中是对这些策略的一种具体实现。

  • 而接口是策略的一个组成部分,它与client端的关系更加密切。
  • ClientInterface中定义了client期望Server做什么,而server具体类是对client这种要求的一种具体实现。
  • OCP原则要求我们清晰地区分策略和策略的具体实现形式。允许扩展具体的实现形式(开放),同时将这种扩展与策略隔离开来,使其对上层的策略透明(封闭)。

例2:

  1. //---------shape.h-----------------
  2. emum ShapeType{circle,square};
  3. struct Shape{
  4. ShapeType itsType;
  5. };
  6. //---------circle.h-----------------
  7. struct Circle{
  8. ShapeType itsType;
  9. double itsRadius;
  10. CPoint itscenter;
  11. };
  12. //---------square.h-----------------
  13. struct Square{
  14. ShapeType itsType;
  15. double itsSide;
  16. CPoint itsTopLeft;
  17. };

  1. //---------drawAllShapes.cpp----------
  2. typedef struct Shape * ShapePointer;
  3. void DrawAllShapes(ShapePointer list[], int n){
  4. int i;
  5. for (i=0;i<n;i++){
  6. struct Shape* s=list[i];
  7. switch (s->itsType){
  8. case square:
  9. s->Square();
  10. break ;
  11. case circle:
  12. s->DrawCircle();
  13. break ;
  14. }
  15. }}

例2的问题:

这个程序不符合OCP,如果需要处理的几何图形中再加入“三角形”将引发大量的修改。

  • 僵化的

增加Triangle会导致Shape、Square、Circle以及DrawAllShapes的重新编译和部署

  • 脆弱的

因为存在大量的既难以查找又难以理解的Switch和If语句,修改稍有不慎,程序就会莫明其妙的出错

  • 牢固的

想在一个程序中复用DrawAllShapes,都必须带上Circle、Square,即使那个程序不需要他们

 

例2 修改后的设计:

  1. class Shape{
  2. public :
  3. virtual void Draw() const =0;
  4. };
  5. class Square: public Shape{
  6. public :
  7. virtual void Draw() const ;
  8. };
  9. class Circle: public Shape{
  10. public :
  11. virtual void Draw() const ;
  12. };

  1. void DrawAllShapes(Vector<Shape*>&
  2. list){
  3. vector<Shape*>::iterator i;
  4. for (i=list.begin();i!=list.end();i++)
  5. (*i)->Draw();
  6. }


完全封闭了吗?

  • 上述代码并不完全封闭——“如果我们希望正方形在所有圆之前绘制”会怎么样?——对绘图的顺序无法实现封闭
  • 更糟糕的是,刚才的设计反而成为了实现“正方形在所有圆之前绘制”功能的障碍。

小结:

  • 一般而言,无论模块多么“封闭”,都会存在一些无法对之封闭的变化
没有对所有变化的情况都封闭的模型
  • 我们怎么办?
既然不可能完全封闭,我们必须有策略的对待此问题——对模型应该封闭那类变化作出选择,封闭最可能出现的变化
----这需要对领域的了解,丰富的经验和常识。
--------错误的判断反而不美,因为OCP需要额外的开销(增加复杂度)
----敏捷的思想——我们预测他们,但是直到我们发现他们才行动

OCP----封装思想的体现

对可变性的封装原则:

  • 找到一个系统的可变因素,将之封装起来。
  • 考虑你的设计中什么会发生变化-------对应思路:什么会导致设计改变

具体的:

  • 一种可变性不应该散落在代码的很多角落里,而应被封装在一个对象里。(继承可看作封装变化的方法。)
  • 一种可变性不应与另一种可变性混在一起。(继承层次不应太多。)

相应设计模式:

  • Strategy
  • Simple Factory
  • Factory Method
  • Abstract Factory
  • Builder
  • Bridge
  • Façade
  • Mediator
0
1
分享到:
评论
1 楼 tamsiuloong 2011-10-08  
对扩展是开放的  对更改是封闭的

相关推荐

    uml与设计模式

    用uml软件介绍设计模式

    面向对象程序设计六大原则

    一、“开-闭”原则(Open-Closed Principle,OCP) 1.1“开-闭”原则的定义及优点 1)定义:一个软件实体应当对扩展开放,对修改关闭( Software entities should be open for extension,but closed for modification....

    类的设计原则

    开闭原则(OCP:Open-ClosedPrinciple)是指在进行面向对象设计(OOD:ObjectOrientedDesign)中,设计类或其他程序单位时,应该遵循:-对扩展开放(open)-对修改关闭(closed)的设计原则。开闭原则是判断面向对象...

    七大原则&&六大关系

    开-闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的...

    设计模式,软件开发者必读

    1.5.1 开-闭原则(OPEN-CLOSED PRINCIPLE, OCP): 7 1.5.2 里氏代换原则(LISKOV SUBSTITUTION PRINCIPLE,常缩写为.LSP) 8 1.5.3 依赖倒置原则(DEPENDENCE INVERSION PRINCIPLE) 8 1.5.4 接口隔离原则(INTERFACE ...

    程序员需要经常刷题吗-cleancode-notes:干净的代码-RobertC.Martin的敏捷软件Craft.io手册-笔记

    开闭原则 - 类应该对扩展开放,对修改关闭。 SRP - 单一职责原则 - 一个类应该有一个,而且只有一个,改变的理由。 DIP - 依赖倒置原则 - 类应该依赖于抽象,而不是具体的细节。 第1章干净的代码 编写干净的代码需要...

    C#面向对象设计的七大原则

    1. 开闭原则(Open-Closed Principle, OCP) 定义:软件实体应当对扩展开放,对修改关闭。这句话说得有点专业,更通俗一点讲,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能...

    Java软件设计模式精讲

    本章将详细介绍开闭原则(OCP)、依赖倒置原则(DIP)、单一职责原则(SRP)、接口隔离原则(ISP)、迪米特法则(LoD)、里氏替换原则(LSP)、合成复用原则(CARP)的具体内容。 为什么需要学习这门课程? 你在...

    浅谈C#设计模式之开放封闭原则

    在软件设计模式证这种不能修改,但可以扩展的思想也是最重要的设计原则,他就是开放-封闭原则 (OCP) 对于程序设计而言,怎么的设计才能面对需求的改变却可以保持相对的稳定,从而可以使得系统可以再第一个版本的...

    Java设计模式——工厂设计模式

    OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。 DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。 LoD(迪米特法则,Law of ...

    设计模式(四)——工厂模式

    OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。 DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程(不要让类继承具体类,而是继承...

    Java与模式(含示例代码)

    4.3 与其他设计原则的关系...............47 4.4 策略模式对“开–闭”原则的支持 ........49 4.5 在其他设计模式中的体现...........50 4.6 一个重构做法的讨论...................54 第5 章 专题:Java 语言的接口...

Global site tag (gtag.js) - Google Analytics