软件设计模式详解:OCP原则
看到两篇关于OCP的文章, 纳之.
原文: http://www.cnblogs.com/muzongyan/archive/2010/08/05/1793454.html
http://blog.csdn.net/beyondhaven/article/details/6821091
定义:
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则的含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有代码来实现变化。
软件实体包括以下几个部分:
- 项目或软件产品中按照一定的逻辑规则划分的模块
- 抽象和类
- 方法
开闭原则是为软件实体的未来事物而制定的对现行开发设计进行约束的一个原则。
注意:开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段了。
变化的类型:
- 逻辑变化
- 子模块变化
- 可见试图变化
一个项目的基本路径应该是这样的:项目开发、重构、测试、投产、运维,其中的重构可以对原有的设计和代码进行修改,运维尽量减少对原有代码修改,保持历史代码的纯洁性,提高系统的稳定性。
开闭原则的重要性:
世界是变化的(而且变化很快),软件是对现实的抽象。---->软件必须能够扩展。
如果任何修改都需要改变已经存在的代码,那么可能导致牵一发动全身现象,进而导致雪崩效应,使软件质量显著下降。
软件实体(类、模块、函数等)应该是可以扩展的,同时还可以是不必修改的,更确切的说,函数实体应该:
(1)对扩展是开放的
当应用的需求变化时,我们可以对模块进行扩展,使其具有满足改变的新的行为。即:我们可以改变模块的功能
(2)对更改是封闭的
对模块进行扩展时,不必改动模块已有的源代码或二进制代码。
- 开闭原则对测试的影响
开闭原则可是保持原有的测试代码仍然能够正常运行,我们只需要对扩展的代码进行测试就可以了。
- 开闭原则可以提高复用性
在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用,粒度越小,被复用的可能性就越大。
- 开闭原则可以提高可维护性
- 面向对象开发的要求
如何使用开闭原则:
- 抽象约束
第一,通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;
第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
第三,抽象层尽量保持稳定,一旦确定即不允许修改。
- 元数据(metadata)控制模块行为
元数据就是用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
Spring容器就是一个典型的元数据控制模块行为的例子,其中达到极致的就是控制反转(Inversion of Control)
- 制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程中指定了所有人员都必须遵守的约定,对项目来说,约定优于配置。
- 封装变化
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口或者抽象类中;
第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
实现OCP的关键是抽象:
例1:既不开放也不封闭的Client:
问题:
client和server都是具体类,接口与实现没有实现分离。如果我们想要让client调用一个新的server类,那么我们不得不修改client的源代码。从而带来编译、链接、部署等一系列的问题。
class client{
server& s;
public:
client(server& SER):s(SER) {}
void useServer(){
s.ServerFunc();
}
};
class server{
int serverData;
public:
void ServerFunc();
};
修改后的设计:
- 设计中ClientInterfece类是一个拥有抽象成员函数的抽象类。Client类使用一个抽象类,然而Client的对象却是用Server类的派生类的对象。
- 如果希望Client对象使用一个不同的服务器类,那么只需从ClientInterfece类派生一个新的类,无需对Client类做任何改动。
class client{
ClientInterface& ci;
public:
client(ClientInterface &
CI):ci(CI){}
void useServer(){
ci.ServerFunc();
}
};
问题:
为什么上述的ClientInterface这个类要取这么个名字,而不叫AbastractServer?
其实这里面蕴含了一个思想:
——client类中更多的描述了高层的策略,而Server类中是对这些策略的一种具体实现。
- 而接口是策略的一个组成部分,它与client端的关系更加密切。
- ClientInterface中定义了client期望Server做什么,而server具体类是对client这种要求的一种具体实现。
- OCP原则要求我们清晰地区分策略和策略的具体实现形式。允许扩展具体的实现形式(开放),同时将这种扩展与策略隔离开来,使其对上层的策略透明(封闭)。
例2:
//---------shape.h-----------------
emum ShapeType{circle,square};
struct Shape{
ShapeType itsType;
};
//---------circle.h-----------------
struct Circle{
ShapeType itsType;
double itsRadius;
CPoint itscenter;
};
//---------square.h-----------------
struct Square{
ShapeType itsType;
double itsSide;
CPoint itsTopLeft;
};
例2的问题:
这个程序不符合OCP,如果需要处理的几何图形中再加入“三角形”将引发大量的修改。
- 僵化的
增加Triangle会导致Shape、Square、Circle以及DrawAllShapes的重新编译和部署
- 脆弱的
因为存在大量的既难以查找又难以理解的Switch和If语句,修改稍有不慎,程序就会莫明其妙的出错
- 牢固的
想在一个程序中复用DrawAllShapes,都必须带上Circle、Square,即使那个程序不需要他们
例2 修改后的设计:
class Shape{
public:
virtual void Draw() const=0;
};
class Square:public Shape{
public:
virtual void Draw() const;
};
class Circle:public Shape{
public:
virtual void Draw() const;
};
完全封闭了吗?
- 上述代码并不完全封闭——“如果我们希望正方形在所有圆之前绘制”会怎么样?——对绘图的顺序无法实现封闭
- 更糟糕的是,刚才的设计反而成为了实现“正方形在所有圆之前绘制”功能的障碍。
小结:
- 一般而言,无论模块多么“封闭”,都会存在一些无法对之封闭的变化
- 我们怎么办?
----这需要对领域的了解,丰富的经验和常识。
--------错误的判断反而不美,因为OCP需要额外的开销(增加复杂度)
----敏捷的思想——我们预测他们,但是直到我们发现他们才行动
OCP----封装思想的体现
对可变性的封装原则:
- 找到一个系统的可变因素,将之封装起来。
- 考虑你的设计中什么会发生变化-------对应思路:什么会导致设计改变
具体的:
- 一种可变性不应该散落在代码的很多角落里,而应被封装在一个对象里。(继承可看作封装变化的方法。)
- 一种可变性不应与另一种可变性混在一起。(继承层次不应太多。)
软件设计模式详解:OCP原则的更多相关文章
- 16个PHP设计模式详解
说明:该教程全部截选自实验楼教程[16个PHP设计模式详解]:主要介绍16个常用的设计模式的基础概念和技术要点,通过UML类图帮助理解设计模式中各个类之间的关联关系,针对每种设计模式都使用PHP完成了 ...
- [ 转载 ] Java开发中的23种设计模式详解(转)
Java开发中的23种设计模式详解(转) 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...
- android java 设计模式详解 Demo
android java 设计模式详解 最近看了一篇设计模式的文章,深得体会,在此基础我将每种设计模式的案例都写成Demo的形式,方便读者研究学习, 首先先将文章分享给大家: 设计模式(Design ...
- Java温故而知新(5)设计模式详解(23种)
一.设计模式的理解 刚开始“不懂”为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式,目 ...
- JAVA设计模式简介及六种常见设计模式详解
一.什么是设计模式 ...
- Javascript设计模式详解
Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...
- Javascript常用的设计模式详解
Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...
- PHP常用设计模式,PHP常用设计模式详解,PHP详解设计模式,PHP设计模式
PHP常用设计模式详解 单例模式: php交流群:159789818 特性:单例类只能有一个实例 类内__construct构造函数私有化,防止new实例 类内__clone私有化,防止复制对象 设置 ...
- Linux ssh登录和软件安装详解
阿哲Style Linux第一天 ssh登录和软件安装详解 Linux学习第一天 操作环境: Ubuntu 16.04 Win10系统,使用putty_V0.63 本身学习Linux就是想在服务器 ...
随机推荐
- Spring Security(11)——匿名认证
目录 1.1 配置 1.2 AuthenticationTrustResolver 对于匿名访问的用户,Spring Security支持为其建立一个匿名的AnonymousAuthe ...
- MongoDB数据模型(二)
原文地址 接上一篇 四.模型树结构 父引用的模型树结构 这个数据模型描述了一个树形结构,在子节点中存储父节点的引用. 模式 父引用模式存储每个树节点到文档中,除了树节点外,文档还存储了父节点的id. ...
- JVM基础(5)-垃圾回收机制
一.对象引用的类型 Java 中的垃圾回收一般是在 Java 堆中进行,因为堆中几乎存放了 Java 中所有的对象实例.谈到 Java 堆中的垃圾回收,自然要谈到引用.在 JDK1.2 之前,Java ...
- ActiveMQ in Action(5) - Clustering
关键字: activemq 2.5 Clustering ActiveMQ从多种不同的方面提供了集群的支持.2.5.1 Queue consumer clusters ActiveMQ支持 ...
- [kuangbin带你飞]专题四 最短路练习 POJ 3268 Silver Cow Party
题意: 在一个有向图中求n头牛从自己的起点走到x再从x走回来的最远距离 思路一开始是暴力跑dij…… 讲道理不太可能…… 然后就百度了一下 才知道把矩阵转置的话就只需要求两次x的单源最短路…… /* ...
- java邮件发送(以163邮箱为例)
1.首先应该开通163邮箱的smtp和pop3,得到授权码 2.其次建立一个web项目,否则需要倒jar包mail.jar 3.创建一个类 4.注意:邮件内容必须为正式话语,否则系统会认为是垃圾邮件而 ...
- 第一题 (Map)利用Map,完成下面的功能:
从命令行读入一个字符串,表示一个年份,输出该年的世界杯冠军是哪支球队.如果该 年没有举办世界杯,则输出:没有举办世界杯. 附:世界杯冠军以及对应的夺冠年份,请参考本章附录. 附录 1.历届世界杯冠 ...
- KVM 虚拟化基本搭建
KVM虚拟化技术 KVM是基于x86架构上Linux操作系统的全虚拟化解决方案 ,在Centos6.3系统中,kvm已经被集成到内核中,相当于使用内核来做虚拟机管理程序.由于KVM本身就工作于内核环境 ...
- Linux RCU机制详解
关于rcu的几点声明: 1:RCU使用在读者多而写者少的情况.RCU和读写锁相似.但RCU的读者占锁没有任何的系统开销.写者与写写者之间必须要保持同步,且写者必须要等它之前的读者全部都退出之后才能释放 ...
- 对SNS网站现状和未来的一些想法——以我对人人网的体验为例
现在对人人网越来越没有兴趣了,上面的照片.状态也越来越少了,反而是朋友圈里大家比较活跃. 我觉得在网上发内容的,至少是希望得到大家关注的,可是为什么人人越来越被大家嫌弃了呢? 人人上的消息越来越被淹没 ...