🌵概述
- 该原则主要说明的是扩展开放,对修改关闭,即尽量通过扩展软件实体类等来解决需求变化,而不是通过修改已有的代码来完成变化;
- 实现开闭原则的核心思想就是面向抽象编程,强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性;
- 简单理解就是说将功能模块以接口的方式来调用,对功能进行抽象化,并且外部能够实现该功能(接口)。
- 在一个软件产品在生命周期内,都会发生变化、升级和维护等一系列原因,可能需要对软件原有代码进行修改,也会有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化,以此提高项目的稳定性和灵活性;
- 而举一个生活例子,在现在的互联网公司中,都比较流行一个弹性化工作制,规定每天工作8小时。这个每天工作8小时这个规定是关闭的,但是什么时候来、什么时候走是开放的,早来早走,晚来晚走。因为晚上加班都是时常发生的事情,你有可能凌晨1点才上班,如果第二天要求你9点就得来上班,我相信你估计是起不来的,甚至到公司都会打瞌睡的状态。
🌾特点
开闭原则是编程中最基础最重要的设计原则,如果遵循开闭原则将有以下优点 :
- 提高系统的复用性。代码粒度越小,被复用的可能性就越大。开闭原则的设计保证系统是实现了复用的系统。
- 提高系统的可维护性。一个产品上线后,维护人员的工作不仅仅是对数据进行维护,还可能对程序进行扩展。开闭原则对已有软件模块的要求不能再修改,去扩展一个新类,这就使变化中的软件系统有一定的稳定性和延续性,便于系统的维护。
- 提高系统的灵活性。我们要知道,需求是无时无刻在变化的,在软件系统面临新的需求时,系统的设计必须是稳定的。开闭原则可以通过扩展已有的软件系统,提供新的行为,能快速应对变化,以满足对软件新的需求,使变化中的软件系统有一定的适应性和灵活性。
🌿问题引出
遥想当初上学时期,甚至在高中的时候,每天的时间都会被大量的习题集,试卷,课后作业占据,除了刷题,就是刷题了,似乎只要刷多了,你就会了。哈哈这里就不细讲当初刷题的苦逼日子了。接下来就以这些习题集等例子来讲解开闭原则吧。
整个习题集的类图如图所示:
1. 习题接口:IExercise:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#008000">/**
* 习题接口,主要有价格和姓名
*/</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">interface</span> <span style="color:#a31515">IExercise</span> {
Double <span style="color:#a31515">getPrice</span>();
String <span style="color:#a31515">getName</span>();
}
</code></span></span>
2. 高中习题类实现了习题接口:HighExercise:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#008000">/**
* 高中习题类
*/</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">HighExercise</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">IExercise</span> {
<span style="color:#008000">// 习题价格</span>
<span style="color:#0000ff">private</span> Double price;
<span style="color:#008000">// 习题名字</span>
<span style="color:#0000ff">private</span> String name;
<span style="color:#008000">// 构造方法</span>
<span style="color:#0000ff">public</span> <span style="color:#a31515">HighExercise</span>(Double price, String name) {
<span style="color:#0000ff">this</span>.price = price;
<span style="color:#0000ff">this</span>.name = name;
}
<span style="color:#0000ff">public</span> Double <span style="color:#a31515">getPrice</span>() {
<span style="color:#0000ff">return</span> <span style="color:#0000ff">this</span>.price;
}
<span style="color:#0000ff">public</span> String <span style="color:#a31515">getName</span>() {
<span style="color:#0000ff">return</span> <span style="color:#0000ff">this</span>.name;
}
}
</code></span></span>
3. 习题集店贩卖习题:ExerciseStore:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#0000ff">import</span> java.util.ArrayList;
<span style="color:#0000ff">import</span> java.util.List;
<span style="color:#008000">/**
* 习题店 专门卖习题集的
*/</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ExerciseStore</span> {
<span style="color:#008000">// 习题店中包含各种习题集</span>
<span style="color:#0000ff">private</span> List<IExercise> exerciseList = <span style="color:#0000ff">new</span> <span style="color:#a31515">ArrayList</span><>();
<span style="color:#008000">// 初始化习题集</span>
<span style="color:#0000ff">public</span> <span style="color:#a31515">ExerciseStore</span>() {
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">HighExercise</span>(<span style="color:#880000">63d</span>, <span style="color:#a31515">"五年高考三年模拟数学"</span>));
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">HighExercise</span>(<span style="color:#880000">53d</span>, <span style="color:#a31515">"五年高考三年模拟语文"</span>));
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">HighExercise</span>(<span style="color:#880000">43d</span>, <span style="color:#a31515">"五年高考三年模拟英语"</span>));
}
<span style="color:#008000">// 展示习题集方法</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">showExercise</span>() {
System.out.println(<span style="color:#a31515">"~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~"</span>);
System.out.println(<span style="color:#a31515">"习题名\t\t\t\t\t价格\t\t"</span>);
exerciseList.forEach(e-> System.out.println(e.getName() + <span style="color:#a31515">"\t\t¥"</span> + e.getPrice() + <span style="color:#a31515">"元\t\t"</span>));
}
<span style="color:#008000">// 测试调用习题集</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] args) {
<span style="color:#a31515">ExerciseStore</span> <span style="color:#008000">store</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ExerciseStore</span>();
store.showExercise();
}
}
</code></span></span>
4. 演示结果:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java">~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~
习题名 价格
五年高考三年模拟数学 ¥<span style="color:#880000">63.0</span>元
五年高考三年模拟语文 ¥<span style="color:#880000">53.0</span>元
五年高考三年模拟英语 ¥<span style="color:#880000">43.0</span>元
</code></span></span>
按照上面的写法,我们可以轻松写出习题店售卖习题集的过程,但是习题店每逢寒暑假的时候,为了让广大学子都能做上习题,习题店决定按照8.5折的强大优惠力度促进销售习题,而这时候就需要对现有的售卖习题的过程进行修改。如果按照原先的思路的话,就会直接在HighExercise类上,直接将价格修改。
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">HighExercise</span> <span style="color:#0000ff">implements</span> <span style="color:#a31515">IExercise</span> {
<span style="color:#008000">// ....省略其他代码</span>
<span style="color:#0000ff">public</span> Double <span style="color:#a31515">getPrice</span>() {
<span style="color:#0000ff">return</span> <span style="color:#0000ff">this</span>.price * <span style="color:#880000">0.85</span>;
}
}
</code></span></span>
下面就一起来探讨下解决方法吧。
☘️解决方案
如果直接修改原先的习题类的话,就会导致不是遵循了开闭原则了,违反了对修改关闭的原则,所以此时不能直接修改HighExercise类或者是IExercise接口了。而是通过扩展一个类来完成该修改价格的需求。
增加一个子类DiscountHighExercise继承HighExercise类来完成:
注意上面修改的HighExercise类中的getPrice()方法应该恢复原先。
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">DiscountHighExercise</span> <span style="color:#0000ff">extends</span> <span style="color:#a31515">HighExercise</span>{
<span style="color:#0000ff">public</span> <span style="color:#a31515">DiscountHighExercise</span>(Double price, String name) {
<span style="color:#0000ff">super</span>(price, name);
}
<span style="color:#008000">// 重写获取价格方法</span>
<span style="color:#2b91af">@Override</span>
<span style="color:#0000ff">public</span> Double <span style="color:#a31515">getPrice</span>() {
<span style="color:#008000">// 增加价格为原来的85折。</span>
<span style="color:#0000ff">return</span> <span style="color:#0000ff">super</span>.getPrice() * <span style="color:#880000">0.85</span>;
}
}
</code></span></span>
再稍微修改下ExerciseStore类即可:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.ygt.principle.ocp;
<span style="color:#0000ff">import</span> java.util.ArrayList;
<span style="color:#0000ff">import</span> java.util.List;
<span style="color:#008000">/**
* 习题店 专门卖习题集的
*/</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ExerciseStore</span> {
<span style="color:#008000">// 习题店中包含各种习题集</span>
<span style="color:#0000ff">private</span> List<IExercise> exerciseList = <span style="color:#0000ff">new</span> <span style="color:#a31515">ArrayList</span><>();
<span style="color:#008000">// 初始化习题集</span>
<span style="color:#0000ff">public</span> <span style="color:#a31515">ExerciseStore</span>() {
<span style="color:#008000">// 修改地方,将创建类改成DiscountHighExercise</span>
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">DiscountHighExercise</span>(<span style="color:#880000">63d</span>, <span style="color:#a31515">"五年高考三年模拟数学"</span>));
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">DiscountHighExercise</span>(<span style="color:#880000">53d</span>, <span style="color:#a31515">"五年高考三年模拟语文"</span>));
exerciseList.add(<span style="color:#0000ff">new</span> <span style="color:#a31515">DiscountHighExercise</span>(<span style="color:#880000">43d</span>, <span style="color:#a31515">"五年高考三年模拟英语"</span>));
}
<span style="color:#008000">// 展示习题集方法</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">showExercise</span>() {
System.out.println(<span style="color:#a31515">"~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~"</span>);
System.out.println(<span style="color:#a31515">"习题名\t\t\t\t\t价格\t\t"</span>);
exerciseList.forEach(e-> System.out.println(e.getName() + <span style="color:#a31515">"\t\t¥"</span> + e.getPrice() + <span style="color:#a31515">"元\t\t"</span>));
}
<span style="color:#008000">// 测试调用习题集</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">main</span>(String[] args) {
<span style="color:#a31515">ExerciseStore</span> <span style="color:#008000">store</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ExerciseStore</span>();
store.showExercise();
}
}
</code></span></span>
再看下执行结果:
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java">~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~
习题名 价格
五年高考三年模拟数学 ¥<span style="color:#880000">53.55</span>元
五年高考三年模拟语文 ¥<span style="color:#880000">45.05</span>元
五年高考三年模拟英语 ¥<span style="color:#880000">36.55</span>元
</code></span></span>
这样通过增加一个DiscountHighExercise类,修改ExerciseStore中少量的代码,就可以实现习题集价格的85折的需求啦,而其他部分没有任何变动,体现了开闭原则的应用。
注意的一点:开闭原则是对扩展开放,对修改关闭,但这并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
🌸 完结
相信各位看官看到这里大致都对设计模式中的其中一个原则有了了解吧,开闭原则实际上的定义就是扩展开放,对修改关闭,提高软件系统的可复用性及可维护性。
学好设计模式,让你感受一些机械化代码之外的程序设计魅力,也可以让你理解各个框架底层的实现原理。最后,祝大家跟自己能在程序员这条越走越远呀,祝大家人均架构师,我也在努力。 接下来期待第三话:依赖倒转原则。 💪💪💪
文章的最后来个小小的思维导图: