设计模式:6.设计模式的七大原则之五(开闭原则)

165 阅读4分钟

开闭原则(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());	// 画三角形
	}
}

这样当我们新增一个三角形时:

  • 新增了一个三角形类,对提供者拓展没有问题
  • 没有修改图形编辑器类,即没有修改使用方,所以以上的修改符合:对提供方拓展开放,对使用方修改关闭的的开闭原则