李天炜

设计模式六大原则 - 开闭原则

开闭原则是设计模式六大原则中最重要也是最“虚”的一个原则。为什么说它最重要呢?因为前面五大原则的目的都是为了实现这个开闭原则,开闭原则相当于它们的主旨思想。为什么说它“虚”呢?因为开闭原则只是个指导思想,不像另外五大原则都有具体可行的指导方法。废话不多说,我们开始了解什么是开闭原则吧。

原文链接:http://tianweili.github.io/blog/2015/02/15/open-close-principle/

定义

Software entities like classes, modules and functions shoule be open for extension but closed for modifications.(一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。)

开闭原则的定义很短,就是对扩展开放,对修改关闭。但是为什么要遵守这一个原则呢?

做过实际项目的筒子们应该都会深有体会,一个软件在其生命周期内都会发生很多变化,这几乎是不可避免的。无论是需求的变化、业务逻辑的变化、程序代码的变化等等,这些变化都有可能对整个软件的稳定性造成一定的威胁。

而开闭原则就是应对这些变化的,它告诉我们应该通过扩展来实现变化,而不是通过修改已有的代码。

例子

我记得上中学的时候,每次考试出成绩的时候老师都会站在讲台上一遍发卷子一遍念出每位同学的分数,下面的学生们心理都暗自捏了一把汗。对于念出自己分数的同学,如果考得好,走上讲台领会卷子,自然心中倍感自豪。但是如果考得不好,一路上感觉都很惭愧啊,此时就很不希望老师当众把自己分数念出来。

下面我们来看看这种场景下的UML类图:

程序代码如下:

学生类,每个学生都有姓名和成绩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private String name;
private String grade;
public Student(String name, String grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public String getGrade() {
return grade;
}
}

老师类,每个老师管理一群的学生:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ArrayList;
import java.util.List;
public class Teacher {
public final static List<Student> students = new ArrayList<Student>();
static {
students.add(new Student("张三", "60"));
students.add(new Student("李四", "70"));
students.add(new Student("王五", "80"));
}
}

场景类:

1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Teacher teacher = new Teacher();
for (Student student : teacher.students) {
System.out.println("姓名:" + student.getName() + " 成绩:" + student.getGrade());
}
}
}

输出结果:

1
2
3
姓名:张三 成绩:60
姓名:李四 成绩:70
姓名:王五 成绩:80

最后王同学看到自己成绩不错,裂开嘴笑了。而张同学就惨了,差点不及格,在全班同学的哄笑中领走了试卷。

更改需求

因为这种当众念出学生成绩的行为可能为伤害到一些成绩不是那么好的同学的自尊心,所以临时决定要对这种念出分数的方式进行改革。把同学们的成绩按照级别来分,分别有优秀,良好,一般,及格,不及格这几种。这样可以照顾成绩不好的同学的自尊心。

那么我们怎么根据这种需求来更改我们的软件呢?

有人说直接修改Student类的getGrade方法不就行了嘛。可能有很多人在实际项目中都是这么做的,但是这就违背了开闭原则,开闭原则要求我们尽量不要修改已有的代码,尽量通过扩展来实现改变。

因为我们举的例子比较简单,但是在实际复杂的项目中,首先理解已有的方法业务逻辑可能就不是一件容易的事情,更何况如果再有其他实体类需要调用你已有的方法,如果你修改了这个方法,所有调用这个方法的代码都得需要找到并修改,这是一件既困难又不安全的做法。

再者说修改了getGrade方法,那么这下只能知道学生成绩处于哪一个层次了,老师也无法知道学生的具体分数了。

那么我们应该怎么做呢?

既然前门我们知道了开闭原则,我们就要用上。我们可以通过扩展已有的代码来实现改变,可以增加一个LevelStudent来继承Student,并扩展修改getGrade方法。

修改后的UML类图如下所示:

增加LevelStudent类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LevelStudent extends Student {
public LevelStudent(String name, String grade) {
super(name, grade);
}
@Override
public String getGrade() {
String level = null;
int grade_ = Integer.valueOf(super.getGrade());
if (grade_ >= 90) {
level = "优秀";
} else if (grade_ >= 80 && grade_ < 90) {
level = "良好";
} else if (grade_ >= 70 && grade_ < 80) {
level = "一般";
} else if (grade_ >= 60 && grade_ < 70) {
level = "及格";
} else if (grade_ < 60) {
level = "不及格";
}
return level;
}
}

总结

开闭原则是对扩展开放,对修改关闭。

开闭原则的主旨是为了拥抱变化。

在六大原则中,开闭原则只是一个思想,没有具体实际操作方法。其他五大原则都是为了实现这个开闭思想的一些方法和工具。

想要遵守开闭原则,就需要一个设计合理的系统。可以说在做系统设计的时候就要考虑到未来的扩展和改变。


作者:李天炜

原文链接:http://tianweili.github.io/blog/2015/02/15/open-close-principle/

转载请注明作者及出处,谢谢。