第9章 OCP:开放-封闭原则

  软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。

9.1 OCP概述

  遵循开放-封闭原则设计出的模块具有两个主要特征:

  (1)对于扩展是开放的(open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。

  (2)对于修改是封闭的(closed for modification)。对模块进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。

  在C#或者其他任何OOPL(面向对象程序设计语言)中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这一组任意个可能的行为则表现为可能的派生类。

  模块可能对抽象体进行操作。由于模块依赖于一个固定的抽象体,所以它对于更改可以是封闭的。同时,通过从这个抽象体派生,可以扩展此模块的行为。

  
9.2 Shape应用程序

9.2.1 违反OCP

  查看如下代码:

//--shape.h---------------------------------------
enum ShapeType
{
circle,
square
}; struct Shape
{
ShapeType itsType;
}; //--circle.h---------------------------------------
struct Circle
{
ShapeType itsType;
double itsRadius;
Point itsCenter;
}; void DrawCircle(struct Circle*); //--square.h---------------------------------------
struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
}; void DrawSquare(struct Square*); //--drawAllShapes.cc-------------------------------
typedef struct Shape *private ShapePointer ;
void DrawAllShapes(ShapePointer list[]private ,private int n )
{
int i;
for (i = ; i < n; i++)
{
   struct Shape* s = list[i];
switch (s->itsType)
{
case square:
DrawSquare((struct Square* )s);
break;
case circle:
DrawCircle((struct Circle* )s);
break;
}
}
}

  DrawAllShapes函数不符合OCP,因为它对于新的形状类型的添加不是封闭的。如果希望这个函数能够绘制包含三角形的列表,就必须变更这个函数。事实上,每增加一种新的形状类型,都必须要更改这个函数。

  
9.2.2 遵循OCP 

  查看如下Square/Circle问题的OOD解决方案

    public interface Shape
{
void Draw();
}
public class Square : Shape
{
public void Draw()
{
//draw a square
}
}
public class Circle : Shape
{
public void Draw()
{
//draw a circle
}
}
public void DrawAllShapes(IList shapes)
{
foreach (Shape shape in shapes)
shape.Draw();
}

9.2.3 预测变化和“贴切的”结构

  一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。

  既然不能完全封闭,那么就必须有策略的对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封装做出选择。他必须先猜测出最有可能发生变化的类,然后构造抽象来隔离那些变化。

  这需要设计人员具有一些从经验中获得的预测能力。有经验的设计人员希望自己对用户和应用领域很了解,能够以此来判断各种变化的可能性。然后,它可以让设计对于最有可能发生的变化遵循OCP原则。

  这一点不容易做到。并且在大多数情况下,他们都会猜测错误。

  遵循OCP的代价也是昂贵的。创建适当的抽象是要花费开发时间和精力的。同时,那些抽象也增加了软件设计的复杂性。

  最终,我们会一直等到变化发生时才采取行动!

9.2.4 放置吊钩

  在上世纪,我们会在我们认为可能发生变化的地方“放置吊钩”(hook)。我们觉得这样会使软件灵活一些。

  然而,我们放置的吊钩常常是错误的。更糟的是,即使不使用这些吊钩,也必须要去支持和维护它们,从而就有了不必要的复杂性的臭味。通常,我们更愿意一直等到却是需要那些抽象时再把它放置进去。

9.2.5 使用抽象获得显式封闭

  封闭是建立在抽象的基础上的。因此,为了让DrawAllShapes对于绘制顺序的变化是封闭的。我们需要一种“顺序抽象体”。这个抽象体定义了一个抽象接口,通过这个接口可以表示任何可能的排序策略。

  一个排序策略意味着,给定两个对象,可以推导出先绘制哪一个。C#提供了这样的抽象。IComparable是一个接口,它只提供一个方法:CompareTo。这个方法以一个对象作为输入参数,当接受消息的对象小于、等于、大于参数数对象时,该方法分别返回-1,0,1 。

如果希望Circle先于Square绘制,查看如下代码:

    public interface Shape : IComparable
{
void Draw();
} public class Square : Shape
{
public void Draw()
{
//draw a square
} public int CompareTo(object obj)
{
if (obj is Circle)
{
return ;
}
else
{
return ;
}
}
}
public class Circle : Shape
{
public void Draw()
{
//draw a circle
} public int CompareTo(object obj)
{
if (obj is Square)
{
return -;
}
else
{
return ;
}
}
}
public void DrawAllShapes(ArrayList shapes)
{
shapes.Sort();
foreach (Shape shape in shapes)
shape.Draw();
}

对于这样的代码:

        public int CompareTo(object obj)
{
if (obj is Square)
{
return -;
}
else
{
return ;
}
}

显然不符合OCP。每次创建一个新的Shape类的派生类时,所有的CompareTo()函数都需要改动。

9.2.6 使用“数据驱动”的方法获取封闭性  
  如果我们不要使Shape类的各个派生类之间互不知晓,可以使用表格驱动的方法。表格驱动的形状排序机制:

/// <summary>
/// This comparer will search the priorities
/// hashtable for a shape's type. The priorities
/// table defines the odering of shapes. Shapes
/// that are not found precede shapes that are found.
/// </summary>
public class ShapeComparer : IComparer
{
private static Hashtable priorities = new Hashtable();
static ShapeComparer()
{
priorities.Add(typeof(Circle), );
priorities.Add(typeof(Square), );
}
private int PriorityFor(Type type)
{
if(priorities.Contains(type))
return (int)priorities[type];
else
return ;
}
public int Compare(object o1, object o2)
{
int priority1 = PriorityFor(o1.GetType());
int priority2 = PriorityFor(o2.GetType());
return priority1.CompareTo(priority2);
}
}

修改DrawAllShapes方法:

public void DrawAllShapes(ArrayList shapes)
{
shapes.Sort(new ShapeComparer());
foreach(Shape shape in shapes)
shape.Draw();
}

9.3 结论

  在许多方面,OCP都是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处:灵活性、可重用性以及灵活性。然而,并不是说使用一种面向对象的语言就是遵循了这个原则。对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意。正确的做法是,开发人员仅仅对程序中出现频繁变化的那些部分作出抽象。拒绝不成熟的抽象和抽象本身一样重要。

摘自:《敏捷软件开发:原则、模式与实践(C#版)》Robert C.Martin    Micah Martin 著

转载请注明出处:

作者:JesseLZJ
出处:http://jesselzj.cnblogs.com

敏捷软件开发:原则、模式与实践——第9章 OCP:开放-封闭原则的更多相关文章

  1. Observer观察者模式与OCP开放-封闭原则

    目录 场景引入 在联网坦克项目中使用观察者模式 总结 在学习Observer观察者模式时发现它符合敏捷开发中的OCP开放-封闭原则, 本文通过一个场景从差的设计开始, 逐步向Observer模式迈进, ...

  2. OCP开放封闭原则

    一.定义 软件实体(类.模块.函数等)应该是可以扩展的,但是不可修改. 如果正确的应用了OCP原则,那么 以后在进行同样的改动时,就只需要添加新的代码,不必修改已经正常运行的代码. 二.OCP概述 1 ...

  3. 《敏捷软件开发-原则、方法与实践》-Robert C. Martin读书笔记(转)

    Review of Agile Software Development: Principles, Patterns, and Practices 本书主要包含4部分内容,这些内容对于今天的软件工程师 ...

  4. 敏捷软件开发_实例2<四>

    敏捷软件开发_实例2 上一章中对薪水支付案例的用例和类做了详细的阐述,在本篇会介绍薪水支付案例包的划分和数据库,UI的设计. 包的划分 一个错误包的划分 为什么这个包是错误的: 如果对classifi ...

  5. 北风设计模式课程---开放封闭原则(Open Closed Principle)

    北风设计模式课程---开放封闭原则(Open Closed Principle) 一.总结 一句话总结: 抽象是开放封闭原则的关键. 1."所有的成员变量都应该设置为私有(Private)& ...

  6. 开放封闭原则(Open Closed Principle)

    在面向对象的设计中有很多流行的思想,比如说 "所有的成员变量都应该设置为私有(Private)","要避免使用全局变量(Global Variables)",& ...

  7. C++ 设计模式 开放封闭原则 简单示例

    C++ 设计模式 开放封闭原则 简单示例 开放封闭原则(Open Closed Principle)描述 符合开放封闭原则的模块都有两个主要特性: 1. 它们 "面向扩展开放(Open Fo ...

  8. 敏捷软件开发:原则、模式与实践——第14章 使用UML

    第14章 使用UML 在探索UML的细节之前,我们应该先讲讲何时以及为何使用它.UML的误用和滥用已经对软件项目造成了太多的危害. 14.1 为什么建模 建模就是为了弄清楚某些东西是否可行.当模型比要 ...

  9. 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则

    第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...

  10. 敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则

    第10章 LSP:Liskov替换原则    Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type). 10.1 违反LSP的情形 10.1.1 简单例子 对L ...

随机推荐

  1. 脱壳系列(二) - EZIP 壳

    程序: 运行程序 用 PEiD 查壳 EZIP 1.0 用 OD 打开 按 F8 往下走 这个看似是 OEP 的地方却不是 OEP 因为代码段从 00401000 开始 可以看到,壳伪造了 3 个区段 ...

  2. 基于aop的redis自动缓存实现

    目的: 对于查询接口所得到的数据,只需要配置注解,就自动存入redis!此后一定时间内,都从redis中获取数据,从而减轻数据库压力. 示例: package com.itliucheng.biz; ...

  3. Java规则引擎及JSR-94[转]

      规则引擎简介 Java规则引擎是推理引擎的一种,它起源于基于规则的专家系统.       Java规则引擎将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.Java规则引擎接 ...

  4. JAVASCRIPT数据类型(值类型-引用类型-类型总览)

    值类型:也称为原始数据或原始值(primitive value). 这类值存储在栈(stack)中,栈是内存中一种特殊的数据结构,也称为线性表,栈按照后进先出的原则存储数据,先进入的数据被压入栈底,最 ...

  5. [转] C#-using用法详解

    转载自 WanderOCN的文章 C#-using用法详解 using 关键字有两个主要用途: (一).作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型. (二).作为语句,用于定义一个 ...

  6. mongodb(五)

    mongoDB 启动配置祥讲导出,导入,运行时备份Fsync锁,数据修复用户管理,安全认证 1.启动项 mongod --help 1.1利用config配置文件来启动数据库改变端口为8888 mon ...

  7. leetcode419

    public class Solution { public int CountBattleships(char[,] board) { );//3行 );//4列 ; ; i < row; i ...

  8. django使用mysql

    django使用mysql需要注意在project目录下的__init__.py 中添加:import pymysql pymysql.install_as_MySQLdb()

  9. 使用PowerMap生成地图图文

    地区    GDP(亿)广东省    80854江苏省    77388山东省    68024浙江省    47251河南省    40471四川省    32934湖北省    32665河北省  ...

  10. Android MVP模式简单易懂的介绍方式 (二)

    Android MVP模式简单易懂的介绍方式 (一) Android MVP模式简单易懂的介绍方式 (二) Android MVP模式简单易懂的介绍方式 (三) 上一篇文章我们介绍完了Model的创建 ...