开放-封闭原则:Software Entities Should Be Open For Extension, Yet Closed For Modification
http://www.textcn.com/jishuwz/prosj/netnet/200612/55186.html
大家可能都有这样的体会,要满足各种各样的客户,并且客户的需求经常变化,程序员就是这样的辛苦命,整天改过来改过去,特别用一些非面向对象的语言写的代码,函数的参数变得越来越长,里面的Case情况慢慢增加,函数变得很大。软件几年之后就变得难于理解和维护,软件的生命周期好像就要终止。如果能够扩展模块的功能,同时又不修改原来已经测试通过的代码,那多好啊!
这完全是可以实现的,关键是抽象。把可能的变化用抽象来隔离它。面向接口编程,而不是面向对象编程,能增强程序的灵活性;如Client类调用Server 类,如果我们希望Client对象使用另外一个不通的Server对象,就必须修改Client中使用Server类的地方;如果Client调用 Server的接口就可以避免这种修改,只要生成新的接口实现类,修改Main等初次使用新子类的地方而不需要修改Client类;使用Strategy 模式和Template Method模式是满足OCP的最常用方法。
如果需要适应某种变化就需要对这种变化进行抽象,会增加程序的复杂度。所以设计人员应该熟悉业务和了解客户需求,预测到需要进行抽象的变化。
敏捷建模不建议提前进行各种假想的变化抽象,而是当变化发生第一次的时候抽象这种变化,以后同样的变化就变得很容易。对代码进行重构以保持良好的结构是很重要的,每次抽象都不应该使软件变得越来越僵化。这是非面向对象的语言不具备的优势。
参考:Health King的专栏
满足OCP的设计给系统带来两个无可比拟的优越性.
- 通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性.
- 已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性.
具有这两个优点的软件系统是一个高层次上实现了复用的系统,也是一个易于维护的系统.那么,我们如何才能做到这个原则呢?不能修改而可以扩展,这个看起来是自相矛盾的.其实这个是可以做到的,按面向对象的说法,这个就是不允许更改系统的抽象层,而允许扩展的是系统的实现层.
解决问题的关键在:抽象化.我们让模块依赖于一个固定的抽象体,这样它就是不可以修改的;同时,通过这个抽象体派生,我们就可以扩展此模块的行为功能.如此,这样设计的程序只通过增加代码来变化而不是通过更改现有代码来变化,前面提到的修改的副作用就没有了.
"开-闭"原则如果从另外一个角度讲述,就是所谓的"对可变性封装原则"(Principle of Encapsulation of Variation, EVP).讲的是找到一个系统的可变因素,将之封装起来.在我们考虑一个系统的时候,我们不要把关注的焦点放在什么会导致设计发生变化上,而是考虑允许什么发生变化而不让这一变化导致重新设计.也就是说,我们要积极的面对变化,积极的包容变化,而不是逃避.
[SHALL01]将这一思想用一句话总结为:"找到一个系统的可变因素,将它封装起来",并将它命名为"对可变性的封装原则".
"对可变性的封装原则"意味者两点:
- 一种可变性应当被封装到一个对象里面,而不应当散落到代码的很多角落里面.同一种可变性的不同表象意味着同一个继承等级结构中的具体子类.继承应当被看做是封装变化的方法,而不应当是被认为从一般的对象生成特殊的对象的方法(继承经常被滥用).
- 一种可变性不应当与另外一种可变性混合在一起.从具体的类图来看,如果继承结构超过了两层,那么就意味着将两种不同的可变性混合在了一起.
"对可变性的封装原则"从工程的角度说明了如何实现OCP.如果按照这个原则来设计,那么系统就应当是遵守OCP的.
但是现实往往是残酷的,我们不可能100%的遵守OCP,但是我们要向这个目标来靠近.设计者要对设计的模块对何种变化封闭做出选择.