贯穿设计模式第二话--开闭职责原则
Posted 最爱吃鱼罐头
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贯穿设计模式第二话--开闭职责原则相关的知识,希望对你有一定的参考价值。
🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳
从今天开始,将开启一个专栏,
【贯穿设计模式】
,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。为了能更好的设计出优雅的代码,为了能更好的提升自己的编程水准,为了能够更好的理解诸多技术的底层源码, 设计模式就是基石,万丈高楼平地起,一砖一瓦皆根基。 ✨✨欢迎订阅本专栏✨✨
🥺 本人不才,如果文章知识点有缺漏、错误的地方 🧐,也欢迎各位人才们评论批评指正!和大家一起学习,一起进步! 👀
❤️ 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ❤️
💬 最后,希望我的这篇文章能对你的有所帮助! 🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!
📃前言回顾
在第一篇文章中,我们了解设计模式有七大原则,是我们在学习设计模式的基础,我们也学习到了第一个原则:单一职责原则;
我们来回顾下,它的定义:指对一个类来说,应当只有一个引起它变化的原因,否则类应该被拆分,即一个类应该只有一个职责。
并且我们通过学生上课的例子讲解并认识到各个阶段的代码所遇到的问题,也有优缺点,所以在应用单一职责原则时,只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则。
🌴开闭原则
今天我们要学习的是开闭原则,对扩展开放,对修改关闭。
🌵概述
- 该原则主要说明的是扩展开放,对修改关闭,即尽量通过扩展软件实体类等来解决需求变化,而不是通过修改已有的代码来完成变化;
- 实现开闭原则的核心思想就是面向抽象编程,强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性;
- 简单理解就是说将功能模块以接口的方式来调用,对功能进行抽象化,并且外部能够实现该功能(接口)。
- 在一个软件产品在生命周期内,都会发生变化、升级和维护等一系列原因,可能需要对软件原有代码进行修改,也会有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现变化,以此提高项目的稳定性和灵活性;
- 而举一个生活例子,在现在的互联网公司中,都比较流行一个弹性化工作制,规定每天工作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折的需求啦,而其他部分没有任何变动,体现了开闭原则的应用。
注意的一点:开闭原则是对扩展开放,对修改关闭,但这并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。
🌸 完结
相信各位看官看到这里大致都对设计模式中的其中一个原则有了了解吧,开闭原则实际上的定义就是扩展开放,对修改关闭,提高软件系统的可复用性及可维护性。
学好设计模式,让你感受一些机械化代码之外的程序设计魅力,也可以让你理解各个框架底层的实现原理。最后,祝大家跟自己能在程序员这条越走越远呀,祝大家人均架构师,我也在努力。 接下来期待第三话:依赖倒转原则。 💪💪💪
文章的最后来个小小的思维导图:
🧐 本人不才,如有什么缺漏、错误的地方,也欢迎各位人才们评论批评指正!🤞🤞🤞
🤩 当然如果这篇文章确定对你有点小小帮助的话,也请亲切可爱的人才们给个点赞、收藏下吧,非常感谢!🤗🤗🤗
🥂 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!✨✨✨
💟 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! 🌼🌼🌼
💖 学到这里,今天的世界打烊了,晚安!🌙🌙🌙
「设计模式」六大原则之二:开闭职责小结
文章目录
「设计模式」六大原则系列链接:
「设计模式」六大原则之一:单一职责小结
「设计模式」六大原则之二:开闭职责小结
「设计模式」六大原则之三:里氏替换原则小结
「设计模式」六大原则之四:接口隔离原则小结
「设计模式」六大原则之五:依赖倒置原则小结
「设计模式」六大原则之六:最小知识原则小结
六大原则体现很多编程的底层逻辑:高内聚、低耦合、面向接口编程、面向抽象编程,最终实现可读、可复用、可维护性。
设计模式的六大原则有:
- Single Responsibility Principle:单一职责原则
- Open Closed Principle:开闭原则
- Liskov Substitution Principle:里氏替换原则
- Law of Demeter:迪米特法则(最少知道原则)
- Interface Segregation Principle:接口隔离原则
- Dependence Inversion Principle:依赖倒置原则
把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。
在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。
本文介绍 SOLID 中的第二个原则:开闭原则。
1.开闭原则定义
开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
代码的扩展性是代码质量评判的最重要的标准之一。 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。
开闭原则的核心思想:面向抽象编程。
2. 如何理解“对扩展开放、对修改关闭”?
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。
关于定义,我们有两点要注意。
第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。
3. 如何做到“对扩展开放、修改关闭”?
我们要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,我们要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。
在识别出代码可变部分和不可变部分之后,我们要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。
在众多的设计原则、思想、模式中,最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)。
实际上,多态、依赖注入、基于接口而非实现编程,以及前面提到的抽象意识,说的都是同一种设计思路,只是从不同的角度、不同的层面来阐述而已。这也体现了“很多设计原则、思想、模式都是相通的”这一思想。
4. 如何在项目中灵活应用开闭原则?
有一句话说得好,“唯一不变的只有变化本身”。
即便我们对业务、对系统有足够的了解,那也不可能识别出所有的扩展点,即便你能识别出所有的扩展点,为这些地方都预留扩展点,这样做的成本也是不可接受的。我们没必要为一些遥远的、不一定发生的需求去提前买单,做过度设计。
我们需要在可读性和可维护性做一个平衡。
5. 示例:
举例说明如何用继承体现 开闭原则的。
以某个付费课程为例,首先定义一个课程接口:
public interface ICourse
Integer getId();
String getName();
Double getPrice();
课程有Java、Web、Android、iOS等。这里以 Android 课程为例:
public class AndroidCourse implements ICourse
private Integer Id;
private String name;
private Double price;
public AndroidCourse(Integer id, String name, Double price)
this.Id = id;
this.name = name;
this.price = price;
public Double getPrice()
return this.price;
// 其他 set和 get 省略
新需求来了,双十一要做活动,Android 课程 要打 7 折促销。
如果直接以之前的类 AndroidCourse
,会引入风险,我们如何在不修改原有代码的前提前下,实现价格优惠这个功能呢?
根据开闭原则的指导思想,我们再写一个处理优惠逻辑的类 AndroidDiscountCourse
。
public class AndroidDiscountCourse extends AndroidCourse
public JavaDiscountCourse(Integer id, String name, Double price)
super(id, name, price);
public Double getOriginPrice()
return super.getPrice();
public Double getPrice()
return super.getPrice() * 0.7;
通过继承父类扩展需要的方法,同时可以保留原有的方法,新增自己的方法。
6. 小结:
对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!
开闭原则:基于接口或抽象实现“封闭”,基于实现接口或继承实现“开放”(拓展)。
参考:
《设计模式之美》
《重学 Java 设计模式》
以上是关于贯穿设计模式第二话--开闭职责原则的主要内容,如果未能解决你的问题,请参考以下文章