贯穿设计模式第二话--开闭原则
?????? 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~??????
从今天开始,将开启一个专栏,
【贯穿设计模式】
,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。为了能更好的设计出优雅的代码,为了能更好的提升自己的编程水准,为了能够更好的理解诸多技术的底层源码, 设计模式就是基石,万丈高楼平地起,一砖一瓦皆根基。 ??欢迎订阅本专栏??
?? 本人不才,如果文章知识点有缺漏、错误的地方 ??,也欢迎各位人才们评论批评指正!和大家一起学习,一起进步! ??
?? 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ??
?? 最后,希望我的这篇文章能对你的有所帮助! ?? 点赞 ?? 收藏 ?留言 ?? 都是我最大的动力!
??前言回顾
在第一篇文章中,我们了解设计模式有七大原则,是我们在学习设计模式的基础,我们也学习到了第一个原则:单一职责原则;
我们来回顾下,它的定义:指对一个类来说,应当只有一个引起它变化的原因,否则类应该被拆分,即一个类应该只有一个职责。
并且我们通过学生上课的例子讲解并认识到各个阶段的代码所遇到的问题,也有优缺点,所以在应用单一职责原则时,只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则。
??开闭原则
今天我们要学习的是开闭原则,对扩展开放,对修改关闭。
??概述
- 该原则主要说明的是扩展开放,对修改关闭,即尽量通过扩展软件实体类等来解决需求变化,而不是通过修改已有的代码来完成变化;
- 实现开闭原则的核心思想就是面向抽象编程,强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性;
- 简单理解就是说将功能模块以接口的方式来调用,对功能进行抽象化,并且外部能够实现该功能(接口)。
- 在一个软件产品在生命周期内,都会发生变化、升级和维护等一系列原因,可能需要对软件原有代码进行修改,也会有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化,以此提高项目的稳定性和灵活性;
- 而举一个生活例子,在现在的互联网公司中,都比较流行一个弹性化工作制,规定每天工作8小时。这个每天工作8小时这个规定是关闭的,但是什么时候来、什么时候走是开放的,早来早走,晚来晚走。因为晚上加班都是时常发生的事情,你有可能凌晨1点才上班,如果第二天要求你9点就得来上班,我相信你估计是起不来的,甚至到公司都会打瞌睡的状态。
??特点
开闭原则是编程中最基础最重要的设计原则,如果遵循开闭原则将有以下优点 :
- 提高系统的复用性。代码粒度越小,被复用的可能性就越大。开闭原则的设计保证系统是实现了复用的系统。
- 提高系统的可维护性。一个产品上线后,维护人员的工作不仅仅是对数据进行维护,还可能对程序进行扩展。开闭原则对已有软件模块的要求不能再修改,去扩展一个新类,这就使变化中的软件系统有一定的稳定性和延续性,便于系统的维护。
- 提高系统的灵活性。我们要知道,需求是无时无刻在变化的,在软件系统面临新的需求时,系统的设计必须是稳定的。开闭原则可以通过扩展已有的软件系统,提供新的行为,能快速应对变化,以满足对软件新的需求,使变化中的软件系统有一定的适应性和灵活性。
??问题引出
遥想当初上学时期,甚至在高中的时候,每天的时间都会被大量的习题集,试卷,课后作业占据,除了刷题,就是刷题了,似乎只要刷多了,你就会了。哈哈这里就不细讲当初刷题的苦逼日子了。接下来就以这些习题集等例子来讲解开闭原则吧。
整个习题集的类图如图所示:
1. 习题接口:IExercise:
package com.ygt.principle.ocp;
/**
* 习题接口,主要有价格和姓名
*/
public interface IExercise {
Double getPrice();
String getName();
}
2. 高中习题类实现了习题接口:HighExercise:
package com.ygt.principle.ocp;
/**
* 高中习题类
*/
public class HighExercise implements IExercise {
// 习题价格
private Double price;
// 习题名字
private String name;
// 构造方法
public HighExercise(Double price, String name) {
this.price = price;
this.name = name;
}
public Double getPrice() {
return this.price;
}
public String getName() {
return this.name;
}
}
3. 习题集店贩卖习题:ExerciseStore:
package com.ygt.principle.ocp;
import java.util.ArrayList;
import java.util.List;
/**
* 习题店 专门卖习题集的
*/
public class ExerciseStore {
// 习题店中包含各种习题集
private List<IExercise> exerciseList = new ArrayList<>();
// 初始化习题集
public ExerciseStore() {
exerciseList.add(new HighExercise(63d, "五年高考三年模拟数学"));
exerciseList.add(new HighExercise(53d, "五年高考三年模拟语文"));
exerciseList.add(new HighExercise(43d, "五年高考三年模拟英语"));
}
// 展示习题集方法
public void showExercise() {
System.out.println("~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~");
System.out.println("习题名\t\t\t\t\t价格\t\t");
exerciseList.forEach(e-> System.out.println(e.getName() + "\t\t¥" + e.getPrice() + "元\t\t"));
}
// 测试调用习题集
public static void main(String[] args) {
ExerciseStore store = new ExerciseStore();
store.showExercise();
}
}
4. 演示结果:
~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~
习题名 价格
五年高考三年模拟数学 ¥63.0元
五年高考三年模拟语文 ¥53.0元
五年高考三年模拟英语 ¥43.0元
按照上面的写法,我们可以轻松写出习题店售卖习题集的过程,但是习题店每逢寒暑假的时候,为了让广大学子都能做上习题,习题店决定按照8.5折的强大优惠力度促进销售习题,而这时候就需要对现有的售卖习题的过程进行修改。如果按照原先的思路的话,就会直接在HighExercise类上,直接将价格修改。
package com.ygt.principle.ocp;
public class HighExercise implements IExercise {
// ....省略其他代码
public Double getPrice() {
return this.price * 0.85;
}
}
下面就一起来探讨下解决方法吧。
??解决方案
如果直接修改原先的习题类的话,就会导致不是遵循了开闭原则了,违反了对修改关闭的原则,所以此时不能直接修改HighExercise类或者是IExercise接口了。而是通过扩展一个类来完成该修改价格的需求。
增加一个子类DiscountHighExercise继承HighExercise类来完成:
注意上面修改的HighExercise类中的getPrice()方法应该恢复原先。
package com.ygt.principle.ocp;
public class DiscountHighExercise extends HighExercise{
public DiscountHighExercise(Double price, String name) {
super(price, name);
}
// 重写获取价格方法
@Override
public Double getPrice() {
// 增加价格为原来的85折。
return super.getPrice() * 0.85;
}
}
再稍微修改下ExerciseStore类即可:
package com.ygt.principle.ocp;
import java.util.ArrayList;
import java.util.List;
/**
* 习题店 专门卖习题集的
*/
public class ExerciseStore {
// 习题店中包含各种习题集
private List<IExercise> exerciseList = new ArrayList<>();
// 初始化习题集
public ExerciseStore() {
// 修改地方,将创建类改成DiscountHighExercise
exerciseList.add(new DiscountHighExercise(63d, "五年高考三年模拟数学"));
exerciseList.add(new DiscountHighExercise(53d, "五年高考三年模拟语文"));
exerciseList.add(new DiscountHighExercise(43d, "五年高考三年模拟英语"));
}
// 展示习题集方法
public void showExercise() {
System.out.println("~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~");
System.out.println("习题名\t\t\t\t\t价格\t\t");
exerciseList.forEach(e-> System.out.println(e.getName() + "\t\t¥" + e.getPrice() + "元\t\t"));
}
// 测试调用习题集
public static void main(String[] args) {
ExerciseStore store = new ExerciseStore();
store.showExercise();
}
}
再看下执行结果:
~~~~~~~~~~~~~~本店拥有的高中习题集~~~~~~~~~~~~~~~
习题名 价格
五年高考三年模拟数学 ¥53.55元
五年高考三年模拟语文 ¥45.05元
五年高考三年模拟英语 ¥36.55元
这样通过增加一个DiscountHighExercise类,修改ExerciseStore中少量的代码,就可以实现习题集价格的85折的需求啦,而其他部分没有任何变动,体现了开闭原则的应用。
注意的一点:开闭原则是对扩展开放,对修改关闭,但这并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
?? 完结
相信各位看官看到这里大致都对设计模式中的其中一个原则有了了解吧,开闭原则实际上的定义就是扩展开放,对修改关闭,提高软件系统的可复用性及可维护性。
学好设计模式,让你感受一些机械化代码之外的程序设计魅力,也可以让你理解各个框架底层的实现原理。最后,祝大家跟自己能在程序员这条越走越远呀,祝大家人均架构师,我也在努力。 接下来期待第三话:依赖倒转原则。 ??????
文章的最后来个小小的思维导图:
?? 本人不才,如有什么缺漏、错误的地方,也欢迎各位人才们评论批评指正!??????
?? 当然如果这篇文章确定对你有点小小帮助的话,也请亲切可爱的人才们给个点赞、收藏下吧,非常感谢!??????
?? 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!???
?? 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! ??????
?? 学到这里,今天的世界打烊了,晚安!??????