DesignPattern系列__05开闭原则
介绍
开闭原则是编程设计中最基本、最重要的原则。
定义:一个软件实体如类、方法和模块等,应该对扩展(提供方)开放,对修改(使用方)关闭。用抽象构建框架,用实现扩展细节。
也就是说,在需求发生新的变化时,我们不应该修改原来的代码,而应该通过扩展来满足新的需求。
例子引入
我们要实现一个画图的功能,能够画出圆形、矩形、三角形等,最常见的思路就是利用面向对象的思想,抽象出一个所有图形对象的基类Shape,具体的图形如矩形、圆形灯继承自该类。在Shape中定义一个变量shapeType来保存具体的图形的类型。
定义一个绘图类GraphicEditor,在执行具体的绘图方法(如画一个矩形)时,根据传入的shapeType来执行对应图形的绘制方法。
类图设计如下:
功能初步实现了,但是有什么缺陷吗?让我们来给项目适当的“松松土”:现在我们想要画一个三角形,如何实现呢?
也很简单:再定义一个类Triangle继承自Shape,并且在GraphicEditor修改方法,加入对三角形的类型判断,具体的代码如下:
public class Ocp { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } } //这是一个用于绘图的类 [使用方] class GraphicEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape s) { if (s.shapeType == 1) drawRectangle(s); else if (s.shapeType == 2) drawCircle(s); else if (s.shapeType == 3) drawTriangle(s); } //绘制矩形 public void drawRectangle(Shape r) { System.out.println(" 绘制矩形 "); } //绘制圆形 public void drawCircle(Shape r) { System.out.println(" 绘制圆形 "); } //绘制三角形 public void drawTriangle(Shape r) { System.out.println(" 绘制三角形 "); } } class Shape { int shapeType; } class Rectangle extends Shape { public Rectangle() { super.shapeType = 1; } } class Circle extends Shape { public Circle() { super.shapeType = 2; } } //新增画三角形 class Triangle extends Shape { Triangle() { super.shapeType = 3; } }
OK,新的需求也实现了,现在,发现问题了吗?
我们每次遇见新需求之外,除了定义新的图形类,还要对类GraphicEditor进行修改。
根据前面提到的“开闭原则”中提到的,应该对修改关闭,对扩展开放,我们不应该修改类GraphicEditor,这样会严重影响代码的稳定性和可维护性。
现在,我们尝试按照“开闭原则”来实现这个功能。
根据“开闭原则”,我们应该封装变化,在这里,我们在Shape中定义一个抽象的绘图方法,并在各自实现类内进行具体实现。在类GraphicEditor中,只定义一个接受参数为抽象(Shape)的方法,使得类不再去受到类型影响,满足了“开闭原则”。
具体的代码如下:
public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle()); } } //这是一个用于绘图的类 [使用方] class GraphicEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape s) { s.draw(); } } abstract class Shape { int shapeType; //定义一个抽象的画图方法 public abstract void draw(); } class Rectangle extends Shape { public Rectangle() { super.shapeType = 1; } @Override public void draw() { System.out.println("绘制矩形"); } } class Circle extends Shape { public Circle() { super.shapeType = 2; } @Override public void draw() { System.out.println("绘制圆形"); } } //新增画三角形 class Triangle extends Shape { Triangle() { super.shapeType = 3; } @Override public void draw() { System.out.println("绘制三角形"); } }
在改进的代码中,我们将画图方法进行抽象,定义在基类Shape中,并通过子类各自实现对应的画图方法。并且,对于类GraphicEditor而言,只需定义一个接受基类作为参数的方法即可,代码变得整洁、易于维护。
使用注意事项
在实际使用中,需要注意以下几个方面:
1.抽象约束
这点的含义包含三个意思:
1.通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或者抽象类中没有定义的public方法;
2.参数类型,要尽量使用接口或者抽象类,不应该使用实现类。
3.抽象层作为约束,应该尽量保持稳定,一旦确定不容修改。
2.元数据控制模块行为
在实际开发中,要尽量使用注解或者配置文件来控制程序的行为,减少重复开发。比如搭建ssm框架中,使用注解或者配置文件来注入bean。
3.约定优于配置
对于大家普遍遵循的章程或者约定,我们要严格遵守,这样能减少配置文件的编写。比如MyBatis框架对xml文件的扫描,默认会去和接口同名的包下去查找,只要我们遵循这一约定, 就无需格外配置。
4.封装变化
对变化的封装包括两点:
1.相同的变化,应该封装到一个接口或者抽象类中;
2.不同的变化,应该封装到不同的接口或者抽象类中,不应该有两个不同的变化封装在一个接口或者抽象类中。
一句话总结
开闭原则,是一切设计模式的基础,可以说其他原则和设计模式都是为了实现开闭i原则。
- 深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP
- 快速入门系列--MVC--05行为
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
- 【虫师--系列05】性能测试知多少---吞吐量
- Jenkins系列之-—05 节点配置
- 【从零单排之微软面试100题系列】05之查找最小的k个元素
- 05、React系列之--永不过时的Hello world
- Spark系列05,SparkSQL概念及相关操作
- Kubernetes系列05:深入掌握Service
- Java多线程系列--“基础篇”05之 线程等待与唤醒
- iOS开发系列课程(05) --- 导航视图控制器
- SAP接口编程-RFC系列05 : Table作为输出参数
- ABAP 开发系列(05): ABAP 内表与内表结构
- Java多线程系列--“JUC锁”05之 非公平锁
- 云星数据---Scala实战系列(精品版)】:Scala入门教程054-Scala实战源码-Scala implicit 操作05
- soapUI系列之—-05 JDBC Request & Xpath Match
- 机器学习入门系列05,Classification: Probabilistic Generative Model(分类:概率生成模型)
- SpringMVC经典系列-05深入了解SpringMVC的核心原理---【LinusZhu】
- [Canvas系列]Canvas绘制曲线之arcto_05
- SharePoint【Ribbon系列】-- 05.在Ribbon中添加新控件(针对用户自定义Tab)