开闭原则(Open Closed Principle)
1.基本介绍
- 开闭原则是编程中最重要、最基础的设计原则。
- 一个软件实体:类、函数、模块,应该对扩展开放(对提供方来说),对修改关闭(对使用方来说)。即增加了一个新功能,加了代码并且修改了一些代码,对原有的使用者的代码,并没有修改。用抽象构建框架,用实现拓展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则,以达到开闭的效果。
2.一个违反开闭原则的实际案例
要完成一个画各种图形的类。
/*
* 图形类基类
*/
class Shape{
// 图形类型:1表示圆形,2表示矩形
int type;
}
/*
* 矩形类
*/
class Rectangle extends Shape{
Rectangle(){
super.type = 2;
}
}
/*
* 圆形类
*/
class Circle extends Shape{
Circle(){
super.type = 1;
}
}
/*
* 画图编辑器
*/
class DrawEditor{
// 画图方法,接受Shape对象或其子类对象,然后根据type选择画不同的方法
public void drawShape(Shape s){
if(s.type == 1){
drawCircle(s);
}else if(s.type == 2){
drawRectangle(s);
}
}
// 画矩形方法
public void drawRectangle(Shape r){
sout("画矩形");
}
// 画圆形方法
public void drawCircle(Shape r){
sout("画圆形");
}
}
// 使用画图
class Do{
psvm(){
// 实例化画图编辑器
DrawEditor drawEditor = new DrawEditor();
drawEditor.drawShape(new Circle()); // 画圆形
drawEditor.drawShape(new Rectangle()); // 画矩形
}
}
以上的代码有什么特点?
- 优点是比较好理解,简单易操作
- 缺点是违反ocp原则,即对提供方的拓展开放,对使用方的修改关闭。(这里的提供方和使用方如何理解?看下面)
当我们新增一个图形种类,应该如何增加?
/*
* 新增 三角形类
* 注意,这个三角形类此处就可以理解为提供方,因为后面话画图类使用了它,所以对提供方拓展此处并没有问题
*/
class Triangle extends Shape{
Triangle(){
super.type = 3;
}
}
/*
* 修改 画图编辑器
* 注意,这个画图编辑器使用了各种图形类,即为使用者,这里对使用者进行了修改,则违反了开闭原则中的:对使用方修改关闭。
*/
class DrawEditor{
// 在画图形方法中新增画三角形的判断
public void drawShape(Shape s){
if(s.type == 1){
drawCircle(s);
}else if(s.type == 2){
drawRectangle(s);
}else if(s.type == 3){
drawTriangle(s);
}
}
// 新增画三角形的方法
public void drawTriangle(Shape r){
sout("画三角形");
}
}
// 使用画图
class Do{
psvm(){
// 实例化画图编辑器
DrawEditor drawEditor = new DrawEditor();
drawEditor.drawShape(new Circle()); // 画圆形
drawEditor.drawShape(new Rectangle()); // 画矩形
drawEditor.drawShape(new Triangle()); // 画三角形
}
}
以上代码可以看出,当我们新增一个图形类时:
- 新增一个图形类,因为图形方法是提供方,所以新增提供方是没有问题的。
- 但是我们仍然需要修改画图编辑器,她使用了各种图形类,因此它是使用方,开闭原则中说应对使用方修改关闭,即不能修改使用方,因此这里违反了开闭原则。
3.符合开闭原则的修改
思路:将图形基类Shape做成抽象类,并提供一个抽象方法draw(),当各种图形类继承基类Shape时,必须实现抽象方法draw()。这时,我们新增一个图形时,只需继承基类Shape并且实现这个图形的draw()方法即可。
/*
* 图形类基类,抽象类
*/
abstract class Shape{
// 绘制方法,抽象方法,子类必须实现
public abstract void draw();
}
/*
* 矩形类
*/
class Rectangle extends Shape{
// 实现父类的绘制抽象方法
@Override
public void draw(){
sout("画矩形");
}
}
/*
* 圆形类
*/
class Circle extends Shape{
// 实现父类的绘制抽象方法
@Override
public void draw(){
sout("画圆形");
}
}
/*
* 画图编辑器
*/
class DrawEditor{
// 画图方法,接受Shape对象或其子类对象
public void drawShape(Shape s){
s.draw();
}
}
// 使用画图
class Do{
psvm(){
// 实例化画图编辑器
DrawEditor drawEditor = new DrawEditor();
drawEditor.drawShape(new Circle()); // 画圆形
drawEditor.drawShape(new Rectangle()); // 画矩形
}
}
如果在以上代码上新增一个三角形,就可以看出代码满足了开闭原则了:
/*
* 新增 三角形类
* 注意,这个三角形类此处就可以理解为提供方,因为后面话画图类使用了它,所以对提供方拓展此处并没有问题
*/
class Triangle extends Shape{
// 实现父类的绘制抽象方法
@Override
public void draw(){
sout("画三角形");
}
}
// 使用画图
class Do{
psvm(){
// 实例化画图编辑器
DrawEditor drawEditor = new DrawEditor();
drawEditor.drawShape(new Circle()); // 画圆形
drawEditor.drawShape(new Rectangle()); // 画矩形
drawEditor.drawShape(new Triangle()); // 画三角形
}
}
这样当我们新增一个三角形时:
- 新增了一个三角形类,对提供者拓展没有问题
- 没有修改图形编辑器类,即没有修改使用方,所以以上的修改符合:对提供方拓展开放,对使用方修改关闭的的开闭原则