在上一章节,我们讨论了面向对象设计(OOD)5大特性之一的SRP原则。这一节,我们来看另一重要的原则:OCP原则(开放-闭合原则)。
Software entity should be open for extension, but closed for modification
上节回顾:
1.UML之轻松入门(1)-类图(附:java Comparable接口与Comparator接口用法)
http://blog.csdn.net/woyunowuyuda/article/details/38981647
2.UML之轻松入门(2)-掌握Junit,让我们的开发更高效
http://blog.csdn.net/woyunowuyuda/article/details/39029689
3.UML之轻松入门(3)-SRP做好厨子,让别人编程去吧
http://blog.csdn.net/woyunowuyuda/article/details/39032305
上节我们说一个厨子要满足SRP原则,做好自己的本职工作,我们可以通过继承和接口两种方式实现。但是现在又出现了一个新的问题:本来这个厨子在学校主学川菜专业,然后在湖南找了一份工作,不得不学习湘菜。学就学呗,谁知顶头上司又是广东人,还得学做粤菜。如果对于一上一节定义的Cooker类来说,今天加一个方法,明天再加一个方法,后天发现某个方法不实用了,再删掉。这样,程序显得杂乱无章。不仅没有效率而且很容易出错。为了解决这类问题,我们想到了OCP原则,即一个软件实体(类,模块,函数等)应当为扩展而开放,又为修改而封闭。如何理解这句话呢,我们拿一个最常见的例子:几乎每个人都有一部手机,可能是安卓的也可能是苹果的。你可以无限制的安装新的软件(只要你的内存够大)。如果你想你的壁纸呢,需要你来修改安卓的源代码吗?完全不用,你只需要再安装一个壁纸就OK。这就是我们所说的OCP原则。为我们的扩展而开放,然而你无需改变模块本身,只需要改变模块的周边环境即可。
下面我们依然拿厨子开刀(是对厨师行业的尊敬),在代码层理解OCP原则:
毕业之后只会川菜(这里原谅我用拼音表示),如今他想学习湘菜和粤菜,是直接在这个类上添加方法吗?显然,这样不利于类的扩展和修改。因此我们可以这样来定义:
首先我们定义一个接口CookSkill,里面有cook方法。然后每一个新学习的烹饪技巧都要实现CookSkill这个接口。那么我们扩展的时候就无需改变Cooker以及CookSkill或其他菜系的代码,只需要添加一个类即可。这样我们就满足了OCP原则。
现在都是考驾照的热潮,这个厨师也弄了一个。好嘛,考完驾照发现自己没车,只好找朋友借个车开开。我们可以这么定义:
也就是说这个Cooker引用了Firend的drive方法。但我们发现Firend类的改变或多或少的会影响Cooker类(也许是参数,也许是名称,也许是返回值等),这时候我们说这两个类的耦合性太高了。并不符合OCP原则。此时,我们可以这么来修改:
这时,我们发现Fiend类和Cooker类的关联没那么紧密了,而且我们还可以用一个单元测试实现DriveSkill接口来完成Cooker的测试,而脱离Firend类的束缚。这样我们就满足了OCP的为了修改而封闭的原则。
我们可以总结一下:
1.什么时候该考虑OCP原则
a.当你的模块存在扩展时应考虑OCP原则,提供新的功能
b.当两个类的耦合度太高时应考虑OCP原则,保证重要模块的稳定性
2.如何满足OCP原则
使用抽象如接口即可满足OCP原则。在功能扩展方面,定义一个接口来接受新的功能。在模块保护方面,定义一个接口来减少之间的耦合。
3.OCP原则和其他原则之间的关系
开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。因此,针对开闭原则的实现方法,一直都有面向对象设计的大师费尽心机,研究开闭原则的实现方式。后面要提到的里氏代换原则(LSP)、依赖倒转原则(DIP)、接口隔离原则(ISP)以及抽象类(Abstract Class)、接口(Interace)等等,都可以看作是开闭原则的实现方法。