1 定义
软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。开放-封闭原则主要体现在两个方面:
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
2 如何实现
在OCP中,实现开放封闭的方法就是抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。这是实施开放-封闭原则的基本思路,同时这种机制建立在两个基本的设计原则的基础上,这就是Liskov替换原则和合成/聚合复用原则。
实现OCP原则的设计模式主要有模板模式、策略模式。而封装变化,是实现这一原则的重要手段,将经常发生变化的状态封装为一个类。具体设计模式的编写可以参考如下文章(4.1,4.2章节):
https://blog.csdn.net/weixin_37624828/article/details/106059837
2.1 问题: 一个糟糕的设计
代码清单:
- ShapeType.java --枚举类
- Point.java
- Shape.java
- Square.java
- Circle.java
- OcpDemo1.java
1 ShapeType.java
public enum ShapeType {
/**
* 圆
*/
CIRCLE,
/**
* 方
*/
SQUARE
}
2 Point.java
public class Point {
private double x;
private double y;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public Point(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
3 Shape.java
public abstract class Shape {
ShapeType itsType;
}
4 Square.java
public class Square extends Shape {
double itsSide;
Point itsToLeft;
public Square(ShapeType itsType, double itsSide, Point itsToLeft) {
this.itsType = itsType;
this.itsSide = itsSide;
this.itsToLeft = itsToLeft;
}
public void drawSquare(){
System.out.println("绘制方形");
System.out.println(toString());
}
@Override
public String toString() {
return "Square{" +
"itsSide=" + itsSide +
", itsToLeft=" + itsToLeft +
", itsType=" + itsType +
'}';
}
}
5 Circle.java
public class Circle extends Shape{
double itsRadius;
Point itsCenter;
public Circle(ShapeType itsType, double itsRadius, Point itsCenter) {
this.itsType = itsType;
this.itsRadius = itsRadius;
this.itsCenter = itsCenter;
}
public void drawCircle(){
System.out.println("绘制圆形");
System.out.println(toString());
}
@Override
public String toString() {
return "Circle{" +
"itsRadius=" + itsRadius +
", itsCenter=" + itsCenter +
", itsType=" + itsType +
'}';
}
}
6 OcpDemo1.java
public class OcpDemo1 {
public static void main(String[] args) {
List<Shape> shapeList = new ArrayList<>();
shapeList.add(new Circle(ShapeType.CIRCLE, 1, new Point(1, 2)));
shapeList.add(new Square(ShapeType.SQUARE, 1, new Point(1, 2)));
shapeList.add(new Square(ShapeType.SQUARE, 1, new Point(1, 2)));
shapeList.add(new Circle(ShapeType.CIRCLE, 1, new Point(1, 2)));
for (int i = 0; i < shapeList.size(); i++) {
switch (shapeList.get(i).itsType){
case CIRCLE:
((Circle) shapeList.get(i)).drawCircle();
break;
case SQUARE:
((Square) shapeList.get(i)).drawSquare();
break;
default:
System.out.println("非圆非方");
}
}
}
}
OcpDemo1中的main方法就不符合OCP原则,因为它对新的类型的添加不是封闭的,每次添加一个新类型就需要在switch菜单中添加一个选项。
在实际工作中并非每一个判断条件都像上述选项中这么规范,一般的判断条件都是由逻辑操作符组合而成的复杂判断条件,因此这样的设计是一个糟糕的设计。
2.2 解决方案:遵循OCP
类图
注:蓝色代表Shape类型是可以扩展的
代码清单:
- Shape.java --接口
- Circle.java
- Square.java
- OcpDemo2.java
1 Shape.java
public interface Shape {
/**
* 绘制形状方法
*/
void draw();
}
2 Circle.java
public class Circle implements Shape{
ShapeType itsType;
double itsRadius;
Point itsCenter;
Circle(ShapeType itsType, double itsRadius, Point itsCenter) {
this.itsType = itsType;
this.itsRadius = itsRadius;
this.itsCenter = itsCenter;
}
@Override
public void draw(){
System.out.println("绘制圆形");
System.out.println(toString());
}
@Override
public String toString() {
return "Circle{" +
"itsRadius=" + itsRadius +
", itsCenter=" + itsCenter +
", itsType=" + itsType +
'}';
}
}
3 Square.java
public class Square implements Shape {
ShapeType itsType;
double itsSide;
Point itsToLeft;
public Square(ShapeType itsType, double itsSide, Point itsToLeft) {
this.itsType = itsType;
this.itsSide = itsSide;
this.itsToLeft = itsToLeft;
}
@Override
public void draw(){
System.out.println("绘制方形");
System.out.println(toString());
}
@Override
public String toString() {
return "Square{" +
"itsSide=" + itsSide +
", itsToLeft=" + itsToLeft +
", itsType=" + itsType +
'}';
}
}
4 OcpDemo2.java
public class OcpDemo2 {
public static void main(String[] args) {
List<Shape> shapeList = new ArrayList<>();
shapeList.add(new Circle(ShapeType.CIRCLE, 1, new Point(1, 2)));
shapeList.add(new Square(ShapeType.SQUARE, 1, new Point(1, 2)));
shapeList.add(new Square(ShapeType.SQUARE, 1, new Point(1, 2)));
shapeList.add(new Circle(ShapeType.CIRCLE, 1, new Point(1, 2)));
// 绘制图形
drawShape(shapeList);
}
/**
* 绘制图形
* @param list 图形集合
*/
private static void drawShape(List<Shape> list) {
for (Shape shape : list) {
shape.draw();
}
}
}
如果list需要添加一个新类型时,drawShape方法无需做出修改,只需要实现一个新类型即可,这就遵循了开放-封闭原则。Shape类型是可以扩展的(遵循了开放),drawShape方法无需修改(遵循了封闭)。
3 结论
在许多方面,OCP都是面向对象设计的核心所在。遵循这个原则可以体现面向对象技术的好处(灵活性、可重用性以及可维护性)。但是并不是只要使用面向对象语言就需要遵循这个原则,开发人员应该对程序中频繁变化的部分做出抽象,而不是对程序肆意抽象。抽象和拒绝抽象同样重要