一、简介
概念:
软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。
问题由来:
- 直接在原代码上修改会有风险,可能导致原先功能出现不可预知错误;
- 如果新需求更改频繁,对原先功能就修改越频繁;
- 随着功能的增多,模块的大小也越来越臃肿;
实现方式:
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。(用抽象构建框架,用实现扩展细节)。
二、示例
下面举例说明什么是开闭原则, 以计算不同图形的面积为例,代码如下:
public class OpenClosed {
public static void main(String[] args) {
draw(GraphicalEnum.CIRCLE);
draw(GraphicalEnum.TRIANGLE);
}
public static void draw(GraphicalEnum graphicalEnum) {
switch (graphicalEnum) {
case TRIANGLE:
Triangle triangle = new Triangle(2, 4);
triangle.getArea();
break;
case CIRCLE:
Circle circle = new Circle(2);
circle.getArea();
break;
default:
break;
}
}
}
enum GraphicalEnum {
CIRCLE, TRIANGLE
}
/**
* 圆形
*/
class Circle {
int r;
public Circle(int r) {
super();
this.r = r;
}
public double getArea() {
double area = Math.PI * Math.pow(r, 2);
System.out.println("计算圆形面积: " + area);
return area;
}
}
/**
* 三角形
*/
class Triangle {
int width;
int height;
public Triangle(int width, int height) {
super();
this.width = width;
this.height = height;
}
public double getArea() {
double area = width * height / 2;
System.out.println("计算三角形面积: " + area);
return area;
}
}
运行结果:
通过上面的代码已经成功完成了计算圆形和三角形的面积,但是这样的设计是否合理?假如我需要增加计算长方形、正方形的面积,很显然需要修改上面的很多代码,修改量非常大,如下增加计算正方形面积:
public class OpenClosed {
public static void main(String[] args) {
draw(GraphicalEnum.CIRCLE);
draw(GraphicalEnum.TRIANGLE);
draw(GraphicalEnum.SQUARE);
}
public static void draw(GraphicalEnum graphicalEnum) {
switch (graphicalEnum) {
case TRIANGLE:
Triangle triangle = new Triangle(2, 4);
triangle.getArea();
break;
case CIRCLE:
Circle circle = new Circle(2);
circle.getArea();
break;
case SQUARE:
Square square = new Square(4);
square.getArea();
break;
default:
break;
}
}
}
enum GraphicalEnum {
CIRCLE, TRIANGLE, SQUARE
}
/**
* 圆形
*/
class Circle {
int r;
public Circle(int r) {
super();
this.r = r;
}
public double getArea() {
double area = Math.PI * Math.pow(r, 2);
System.out.println("计算圆形面积: " + area);
return area;
}
}
/**
* 三角形
*/
class Triangle {
int width;
int height;
public Triangle(int width, int height) {
super();
this.width = width;
this.height = height;
}
public double getArea() {
double area = width * height / 2;
System.out.println("计算三角形面积: " + area);
return area;
}
}
/**
* 正方形
*/
class Square {
int width;
public Square(int width) {
super();
this.width = width;
}
public double getArea() {
double area = Math.pow(width, 2);
System.out.println("计算正方形面积: " + area);
return area;
}
}
可见,修改的地方包括提供者,也包括使用方,按照开闭原则:“对扩展开放,对修改关闭”,上面的代码明显违反了开闭原则,在扩展功能的同时需要修改原先运行正常的代码,如果扩展的功能越多,那么修改后导致的问题可能就会越多。试想一下,是否可以利用多态的思想实现如上的需求,并且扩展性是不是也变好了?答案是肯定的,下面我们将定义一个抽象类,然后各个子类去继承该抽象类,抽象类定义好了一些规则,子类只需要根据自身需要重写方法即可。
三、优化
优化后的类图大致如下:
public class OpenClosed {
public static void main(String[] args) {
Circle circle = new Circle(2);
draw(circle);
Triangle triangle = new Triangle(2, 4);
triangle.getArea();
Square square = new Square(4);
square.getArea();
}
public static void draw(Graphical graphical) {
graphical.getArea();
}
}
/**
* 图形抽象类,方便后期扩展功能
*/
abstract class Graphical {
public abstract double getArea();
}
/**
* 圆形
*/
class Circle extends Graphical {
int r;
public Circle(int r) {
super();
this.r = r;
}
@Override
public double getArea() {
double area = Math.PI * Math.pow(r, 2);
System.out.println("计算圆形面积: " + area);
return area;
}
}
/**
* 三角形
*/
class Triangle extends Graphical {
int width;
int height;
public Triangle(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public double getArea() {
double area = width * height / 2;
System.out.println("计算三角形面积: " + area);
return area;
}
}
/**
* 正方形
*/
class Square extends Graphical {
int width;
public Square(int width) {
super();
this.width = width;
}
@Override
public double getArea() {
double area = Math.pow(width, 2);
System.out.println("计算正方形面积: " + area);
return area;
}
}
运行结果:
以后如果想扩展更多的图形的话,只需要新增一个类继承抽象类即可,原先代码保持不变,这就是对扩展开放(针对提供者来说),对修改关闭(针对使用方来说)。
四、总结
开闭原则告诉我们,一个软件实体应该通过扩展来实现变化, 而不是通过修改已有的代码来实现变化。即:
- 用抽象构建框架,用实现扩展细节
优点:
- 可以提高代码的复用性;
- 增强可维护性和可扩展性;
- 通过扩展实现变化,保证对原先已有代码的影响降到最小;
- 遵守好了开闭原则,则能保证模块功能不会变得很臃肿;
开闭原则,不能说遵守不遵守,应该说遵守的或多或少,任何人都不一定对开闭原则做的很好,只能说尽量遵守得足够多,其实五项原则:单一职责、接口隔离、依赖倒置、里氏替换遵守的好的话,自然就遵守了开闭原则,这样的软件架构扩展性才会好一些。在实际工作中,或多或少接触过前人留下的代码需要维护,如果某一天新增需求了,如果作为维护人员,那么你想是通过扩展来实现新需求还是通过读懂前人代码然后进行修改来实现新需求,毋庸置疑,大家肯定都想直接新加一个方法就可以搞定新需求就好了,所以我们在工作中写代码的时候能尽量满足开闭原则,就一定要满足,提高代码可读和可维护性。
好了,以上只是笔者对开闭原则学习的一些总结,可能总结的还不够那么完善,欢迎大家一起补充,一起学习一起进步呀~~~