设计模式基本原则(三)

 

在阅读该篇之前有必要阅读以下《设计模式基本原则(一)》和《设计模式基本原则(二)》,本篇将介绍设计模式的最后...



在阅读该篇之前有必要阅读一下《设计模式基本原则(一)》和《设计模式基本原则(二)》,本篇将介绍设计模式基本原则的最后一个原则:开闭原则。

设计模式六大原则(6):开闭原则 

1988年,勃兰特·梅耶(Bertrand Meyer)在他的著作《面向对象软件构造(Object Oriented Software Construction)》中提出了开闭原则,它的原文是这样的:“Software entities should be open for extension,but closed for modification”。翻译过来就是:“软件实体应当对扩展开放,对修改关闭”。这句话说得略微有点专业,我们把它讲得更通俗一点,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即修改原有的代码对外部的使用是透明的[来自百度百科]。



我觉得开闭原则是六大设计模式的基本原则中定义最模糊的了,但是这个基本原则也是最重要的,因为它其实是对其它五大设计模式基本原则的一个归纳。很多人在写六大设计模式基本原则的文章时,都是先介绍开闭原则,以作为一个论点,但是我觉得先介绍其它五大原则然后再介绍这个原则会使大家有种恍然大悟的感觉,从而在阅读该篇文章之后再去翻看其它五大基本原则的文章的时候,会因此加深对设计模式基本原则的领悟。

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

参与过大型复杂项目的开发者都知道,每一个小的需求变更引起的代码修改都会带来巨大的代价,主要体现在新代码的加入会引入未知的错误从而需要进行大量的测试。在升级新的系统的时候旧的系统完成的功能就有可能彻底的废掉,甚至因为代码的修改需要进行很长的试运行才有可能使新的系统正式投入运行。那么如何才能尽可能既能满足软件项目的后期需求变更又能使系统花费最小的代价并且可预见性的最少引入新的错误呢?开闭原则就确切的告诉我们,可以这样做:在设计软件结构的时候尽量采用抽象和接口来构建,因为抽象和接口能够统一的描述和约束现实世界的对象,所以他们都具有广泛性。同时对于面向对象语言,通过继承和实现能够实现抽象接口和基类引用其实现类和子类,这样当真正完成软件功能的实现类或者子类的职责发生改变的时候,引用类由于只是以多态的方式引用该实现类,所以就和这个实现类实现了松散的耦合,因此只要它仍然是满足原本接口的约束就不会对引用类造成影响。另一方面开闭原则告诉我们,如果实现类的职责发生变更时,如果不是特别必要,不要修改类,而是采用继承的方式以一个新的子类来完成变更后的职责,如此一来,原本的引用接口同样可以引用新的实现类并且对旧的实现类也能够引用。

打个比方说吧,有一个描述黑白打印机的类,后来由于增设了彩色打印机,所以需要新的类来描述这个打印机,彩色打印机是可以完成黑白打印的。一个想法就是把原本的打印机类修改用来兼备描述彩色打印机。但是这样一来,如果改造类出了问题,那么原本能够描述黑白打印机的功能也遭到了破坏。如果采用继承的方式新创建打印机的实现子类,该子类具有描述原本打印功能的特征比如可以打印黑白文件,也有新的用于描述打印彩色的功能,并且一但新的功能出了问题,旧的打印机类仍然可以使用,不会影响到黑白打印机类。

开闭原则就是对单一职责原则、里氏替换原则、依赖倒置原则和接口隔离原则的一个整体归纳,所谓的对扩展开放对修改关闭的定义正是衡量这五个原则使用的合适程度。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

一个好的软件结构其实主要就是看抽象和接口使用的合理程度。利用抽象来架构软件用实现去扩展细节正是开闭原则的核心。说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

最后总结一下六大基本原则:俗话说,任何事情都是一把双刃剑,使用设计模式的基本原则也不例外。比如说单一原则,将职责分散到更小的类而不是将多个职责集中到一个类中,这可以降低类的耦合但是如果将职责划分的太细就会造成大量的小类使设计的软件复杂性反而增加了且其可可阅读性也变差了。再比如说接口隔离原则中如果将接口设计的太小就会出出现完成一个小的功能需要实现很多接口,这样就会出现头重脚轻的感觉,体现在程序中就是结构很混乱。依赖倒置原则建议用接口或者抽象类来依赖子类的基类或者实现类的接口,而不是直接依赖具体子类或者实现类,这体现了抽象灵活性的特点,但是如果任何一个引用都是采用接口或者抽象类的话难免会出现一个简单的不变的类反而莫名奇妙的多了一个基类,这也反而增加了软件结构的复杂性。所以还是一句老话:不要为了遵循原则而去遵循原则,要适度的使用设计模式的基本原则。体现在设计模式当中就是不要为了使用设计模式而使用设计模式。一个好的软件开发人员应该明白什么时候什么场景遵循什么设计原则并且在六大基本原则合理取舍达到软件结构的低耦合高内聚的特点。

以下来自网络:

我们用一个六维图来表示六大基本原则:



图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。



在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。



至此设计模式的基本原则就讲完了,至于其它五个原则,如果大家感兴趣话可以翻看历史。说说我的感受吧,感觉把六个原则一次性的整理出来并且细细研究过,真是受益匪浅。也希望喜欢的人可以好好研究一下。其实我写这个并不是说要写的多么透彻才行,就是只是体现自己对这个东西的理解,大家也可以有自己的理解,如果意见和我的不一致,欢迎交换意见。

--------------------

下一篇将会讲述23种设计模式的第一个模式,至于什么模式,再说。欢迎大家关注我的公众号,我会坚持写自己的东西。


    关注 程序员日记


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册