马上期末了,一直没看懂访问者模式,今天查了好多资料,算是搞明白了。在这分享一下自己的理解。
访问者模式,顾名思义就是遍历集合时,用来访问集合中元素的。这个集合中元素的类型不同,但类型都是已知的,且未来不会改变。访问单个元素的方式有多种,每种方式在访问不同类型的元素时所做的操作不同,并且未来可能会有新的访问方式。
举例来说:
一个班级里有学霸和学渣两种类型的学生。当出成绩时,学霸开心,学渣不开心。当放假时,学霸不开心,学渣开心。未来不会有第三种类型的学生,但未来可能会有新的事件发生,比如布置作业、春游等。
现在要求写代码实现打印放假时和出成绩时学生们的反应,代码怎么写呢?最简单最容易想到的方法,用if语句和instanceof判断类型,执行不同操作,伪代码如下:
abstract class Student{} // 抽象学生类
class XueBa extends Student{} // 学霸
class XueZha extends Student{} // 学渣
main{
List<Student> students = {new XueBa(), new XueZha(), ....}; // 班级学生,只有学霸和学渣两种类型
// 出成绩了
for(Student s: students){
if(s instanceof XueBa){ // 学霸开心
print("出成绩了,开心");
}else if(s instanceof XueZha){ //学渣不开心
print("出成绩了,不开心");
}
}
// 放假了
for(Student s: students){
if(s instanceof XueBa){ //学霸不开心
print("放假了不能学习,不开心");
}else if(s instanceof XueZha){ //学渣开心
print("放假了,开心");
}
}
}
但这样写明显不符合面向对象的开放封闭原则,怎么改呢?有一个容易想到的方法,就是把出成绩和放假作为一个抽象方法,写到抽象学生类里面,学霸和学渣分别实现这两个方法(这种做法也不对,后面会说原因):
abstract class Student{ // 抽象学生类
public void vacation(); // 放假方法
public void releaseScore(); // 出成绩方法
}
class XueBa extends Student{ //学霸实现两个方法
public void vacation(){
print("放假了不能学习,不开心");
}
public void releaseScore(){
print("出成绩了,开心");
}
}
class XueZha extends Student{ // 学渣实现两个方法
public void vacation(){
print("放假了,开心");
}
public void releaseScore(){
print("出成绩了,不开心");
}
}
main{
List<Student> students = {new XueBa(), new XueZha(), ....}; // 班级学生,只有学霸和学渣两种类型
// 出成绩了
for(Student s: students){
s.releaseScore();
}
// 放假了
for(Student s: students){
s.vacation();
}
}
这样写,看上去很符合开放封闭原则,因为如果有新的类型的学生,只需要添加一个新的学生类,让它去实现两个抽象方法即可,不用改其它类的代码。
但仔细看题目,题目说不会有新的类型学生,而是会有新类型的事件!
也就是说,如果现在要春游,就需要在抽象学生类里加一个springOuting()方法,然后分别在学霸和学渣里实现。一共需要改三个类!一点也不符合开放封闭原则。
所以一定要搞清楚固定的东西是什么,可能会变的东西是什么。
因为会变的是遍历列表时对学生的访问方法(出成绩、放假、春游),而不是学生类型(学霸、学渣),所以这里就不应该像上面那样将学生的行为抽象,而是应该将访问的方式抽象:
abstract class Student{ // 抽象学生类
public void accept(Visitor v); // 处理Visitor这个抽象事件。(可能是出成绩、放假等。这里抽象化了,以便随时改)
}
class XueBa extends Student{ // 学霸处理事件
public void accept(Visitor v){
v.visitXueBa(this); // 调用对学霸的处理方法
}
}
class XueZha extends Student{ // 学渣处理事件
public void accept(Visitor v){
v.visitXueZha(this); // 调用对学渣的处理方法
}
}
abstract class Visitor{ // 抽象的访问方式类
public void visitXueBa(XueBa x); // 访问学霸
public void visitXueZha(XueZha x); // 访问学渣
}
这样写好以后,要添加访问方法(出成绩、放假、春游),只需要继承Visitor类,并实现学霸和学渣分别不同的反应即可。比如出成绩:
class ReleaseScoreVisitor extends Visitor{
public void visitXueBa(XueBa x){
print("出成绩了,开心");
}
public void visitXueZha(XueZha x){
print("出成绩了,不开心");
}
}
主函数:
main(){
List<Student> students = {new XueBa(), new XueZha(), ....}; // 班级学生,只有学霸和学渣两种类型
Visitor v = new ReleaseScoreVisitor(); // 先确定访问方式,“出成绩”
for(Student s: students){
s.accept(v); // 学生s接受v方式的访问
}
}
现在要添加访问方法(放假、春游等),就只需要
添加访问方式类,而不需要修改上面写好的类,所以这样才真正符合开放封闭原则。
在遍历集合时,把访问单个对象的 方式抽象出来,这就是访问者模式。
所以说,一定要搞清楚固定的东西是什么,可能会变的东西是什么,才好理解访问者模式的思想。