本文主要是介绍05.开闭原则(Open Closed Principle),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
“你这个人怎么这么轴?让你改改以前的代码怎么和要了你命似的?难道你的能力仅限于此吗?”
“你懂什么?我有我的原则!我有我的信仰!”
一言
开闭原则即:对扩展开放,对修改关闭,它是所有设计的核心。
概述
大家潜意识里普遍遵循的原则,往往是最关键、最重要的原则。法律或许偶有狂徒触碰,但雷池从不放走一个活人。
科幻小说《三体》中有这样一个桥段,面壁者比尔·希恩斯为了“坚定”地球人抵抗三体人侵略的决心,发明了可以改变人类潜意识的机器,并取名思想钢印。甚至当在机器中输入“水是有毒的”这样的理论也可以灌注到人类的基本意志中,比尔希尔斯本人也险些因此拒绝喝水,脱水而亡。
小说的描述颇具浪漫主义气质,事实上,开闭原则就是软件设计中难以推翻的“思想钢印”。
如果单说开闭原则,或许有些同学会一时发懵不知道是在说什么。但如果换种通俗的说法:之前的需求要你实现了30个工具方法,随着项目的丰满,这30个工具方法在各个实现中调用了差不多100次。现在有个狼人过来悄咪咪告诉你,“兄弟,这30个工具方法你把实现细节全改一改,我有大用!” 你会有什么反应?
相信大多数同学都能心领神会的口径一致:“GUN!”
“呀?还会说英文呢?”
“拼音或者英文都可以表达我的态度,具体看你理解!”
狼人不理解了,为什么这么大反应?其实广大程序员这种刻在骨子里的反应就是开闭原则的思想钢印,大多数人潜意识里都在恪守。
何为开闭原则
一个软件实体,模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现优化。
- 对扩展开放: 对提供功能的一方扩展开放,可以添加功能或类;
- 对修改关闭: 对使用方而言,修改是关闭的(比如新增了一个类,不会修改之前使用方的代码/功能);
三寸反骨
碰了璧的狼人黑夜化身反骨仔,挑灯加班决定突破开闭原则的思想钢印,在不遵守开闭原则的前提下,写了一段代码。
需求很简单,实现一个画图形的工具类。
反例代码
public class Graph {class Editor{public void draw(Shape shape){if (shape.type==1)drawCircle(shape);else if (shape.type==2)drawBlock(shape);}public void drawCircle(Shape shape){System.out.println("绘制圆形具体实现");}public void drawBlock(Shape shape){System.out.println("绘制方形具体实现");}}class Shape{int type;}class Circle extends Shape{Circle(){super.type =1;}}class Block extends Shape{Block(){super.type = 2;}}
}
反骨仔开始志得意满,这也太简单了,丝毫没有压力啊。于是此方法投入使用了半年,借助这个工具类,同事们搭建了一个又一个“完美且稳定”的系统,牵扯调用三百多次。
然后,好消息来了,绘图工具需求增加,要求在原有基础上支持对三角形、平行四边形、梯形、五角星、六芒星、小波浪、大波浪等等一百二十多个图形的绘制。
反骨仔凌乱了,因为他的绘制方法实现要做大量修改姑且不论,同事们调用这个方法高达三百余处,现在底层方法修改了,上下文逻辑是不是都需要再推敲,回归测试的工作量有多少。这一些列操作下来的成本又由谁来承担?
所以说,雷池就是雷池,轻易不要头铁,开闭原则能被广大同学在潜意识中遵守一定有他的道理。在软件开发的过程中,也许我不比所有人聪明,但是我一定要是最稳的那个。
优化设计
打开时光机,反骨仔脱胎换骨回到了半年前那个倔强的夜晚,默默捧起了《程序员的自我修养》,开始遵循OCP原则,对那个“简单的需求”进行设计。
优化代码
public class Ocp {public static void main(String[] args) {//使用,看看存在的问题GraphEditor graphEditor = new GraphEditor();graphEditor.drawShape(new Block());graphEditor.drawShape(new Circle());graphEditor.drawShape(new Star());}
}//绘图类
class GraphEditor{//接收Shape对象,根据type绘制不同图形public void drawShape(Shape s){s.draw();}
}//图形基类
abstract class Shape{public abstract void draw();//抽象方法
}//矩形类
class Block extends Shape{@Overridepublic void draw() {System.out.println("绘制方块");}
}//圆形类
class Circle extends Shape{@Overridepublic void draw() {System.out.println("绘制圆形");}
}//圆形类
class Star extends Shape{@Overridepublic void draw() {System.out.println("绘制五角星");}
}
把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类实现即可,这样我们有新的图形种类时,只需要新的图形类继承Shape,并实现draw方法即可,使用的代码就不需要修改,这就满足了OCP原则。
果然,命运的齿轮在半年后再一次转到了曾经的至暗时刻,海量的图形扩展需求蜂拥而至,而此时的那个少年成竹在胸,微微一笑,说到:
“大家放心,已经在用的方法不会有任何变更。扩展的问题交给我,相关模块的回归测试理论上可以不用做,当然,具体的施行还是看各部门的落地要求。”
结
开闭原则是所有设计的核心,更是所有优雅扩展的集中表现形式。从广义上讲,它其实是在寻求一种变中求不变的平衡。开闭原则并不是拒绝需求的扩张,相反,它恰恰是为了以最小代价满足需求扩张而形成的捷径。从另一个角度看,它恰恰印证了软件开发生命周期中,设计阶段的重要性。
在上文的论述中,为了更好的表述OCP原则,我们一直尝试着从正向解决问题。其实我们忽略了一个角度,“反骨仔”遇到的难题一方面是由于他没有遵循OCP原则,那么其他同事有没有问题呢?一个工具类牵扯到几百个模块的调用是否真的合理呢?
这里其实就又牵出了耦合度的问题,当然这是另外一个话题了。
笔者认为,软件设计的本质之一在于对耦合关系的优化处理,通常我们讲“高内聚、低耦合”的系统架构是值得学习和借鉴的(比如springframework中的IOC理论就极大的降低了体系内的耦合度)。
那么在软件设计中,是否有一个原则描述了耦合关系呢?
有的,那就是是 迪米特法则 , 下篇博客我们再展开聊聊耦合处理的二三事。
关注我,共同进步,每周至少一更。——Wayne
这篇关于05.开闭原则(Open Closed Principle)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!