访问者模式

[TOC]

原理

示例

学生(C#实现)

一个班级里有学霸和学渣两种类型的学生。当出成绩时,学霸开心,学渣不开心。当放假时,学霸不开心,学渣开心。
未来不会有第三种类型的学生,但未来可能会有新的事件发生,比如布置作业、春游等

第一版

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Program
{
static void Main(string[] args)
{
var students = new List<Student>
{
new BrainyStudent(),
new InferiorStudent()
//...
};

// 出成绩了
foreach (var student in students)
{
if (student is BrainyStudent)
{
Console.WriteLine("出成绩了,开心");
}
else
{
Console.WriteLine("出成绩了,不开心");
}
}

// 放假了
foreach (var student in students)
{
if (student is BrainyStudents)
{
Console.WriteLine("放假了不能学习,不开心");
}
else
{
Console.WriteLine("放假了不用学习,开心");
}
}
}
}


/// <summary>
/// 抽象学生类
/// </summary>
public abstract class Student
{

}

/// <summary>
/// 学霸
/// </summary>
public class BrainyStudents : Student
{

}

/// <summary>
/// 学渣
/// </summary>
public class InferiorStudent : Student
{

}

但这样写明显不符合面向对象的开放封闭原则,怎么改呢?

初步重构

有一个容易想到的方法,就是把出成绩和放假作为一个抽象方法,写到抽象学生类里面,学霸和学渣分别实现这两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

/// <summary>
/// 抽象学生类
/// </summary>
public abstract class Student
{
/// <summary>
/// 放假方法
/// </summary>
public abstract void Vacation();

/// <summary>
/// 出成绩方法
/// </summary>
public abstract void ReleaseScore();
}

class Program
{
static void Main(string[] args)
{
var students = new List<Student>
{
new BrainyStudent(),
new InferiorStudent()
//...
};

foreach (var student in students)
{
student.ReleaseScore();
student.Vacation();
}
}
}

/// <summary>
/// 抽象学生类
/// </summary>
public abstract class Student
{
/// <summary>
/// 放假方法
/// </summary>
public abstract void Vacation();

/// <summary>
/// 出成绩方法
/// </summary>
public abstract void ReleaseScore();
}

/// <summary>
/// 学霸
/// </summary>
public class BrainyStudent : Student
{
public override void Vacation()
{
Console.WriteLine("放假了不能学习,不开心");
}

public override void ReleaseScore()
{
Console.WriteLine("出成绩了,开心");
}
}

/// <summary>
/// 学渣
/// </summary>
public class InferiorStudent : Student
{
public override void Vacation()
{
Console.WriteLine("放假了不用学习,开心");
}

public override void ReleaseScore()
{
Console.WriteLine("出成绩了,不开心");
}
}
}

这样看上去很符合开放封闭原则,因为如果有新的类型的学生,只需要添加一个新的学生类,让它去实现两个抽象方法即可,不用改其它类的代码。
但仔细看需求,题目说不会有新的类型学生,而是会有新类型的事件!
也就是说,如果现在要春游,就需要在抽象学生类里加一个springOuting()方法,然后分别在学霸和学渣里实现。一共需要改三个类!一点也不符合开放封闭原则。
所以一定要搞清楚固定的东西是什么,可能会变的东西是什么。

使用访问者模式重构

因为会变的是遍历列表时对学生的访问方法(出成绩、放假、春游),而不是学生类型(学霸、学渣),所以这里就不应该像上面那样将学生的行为抽象,而是应该将访问的方式抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/// <summary>
/// 抽象学生类
/// </summary>
public abstract class Student
{
/// <summary>
/// 处理Visitor这个抽象事件。
///(可能是出成绩、放假等。这里抽象化了,以便随时改)
/// </summary>
/// <param name="v"></param>
public abstract void Accept(Visitor v);
}

public abstract class Visitor
{ // 抽象的访问方式类
public abstract void VisitBrainyStudent(BrainyStudent brainyStudent); // 访问学霸
public abstract void VisitInferiorStudent(InferiorStudent inferiorStudent); // 访问学渣
}

/// <summary>
/// 学霸
/// </summary>
public class BrainyStudent : Student
{
/// <summary>
/// 学霸处理事件
/// </summary>
/// <param name="v"></param>
public override void Accept(Visitor v)
{
// 调用对学霸的处理方法
v.VisitBrainyStudent(this);
}
}

/// <summary>
/// 学渣
/// </summary>
public class InferiorStudent : Student
{
/// <summary>
/// 学渣处理事件
/// </summary>
/// <param name="v"></param>
public override void Accept(Visitor v)
{
// 调用对学渣的处理方法
v.VisitInferiorStudent(this);
}
}

这样写好以后,要添加访问方法(出成绩、放假、春游),只需要继承Visitor类,并实现学霸和学渣分别不同的反应即可。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   public class ReleaseScoreVisitor : Visitor
{
public override void VisitBrainyStudent(BrainyStudent brainyStudent)
{
Console.WriteLine("出成绩了,开心");
}

public override void VisitInferiorStudent(InferiorStudent inferiorStudent)
{
Console.WriteLine("出成绩了,不开心");
}
}

public class VacationVisitor : Visitor
{
public override void VisitBrainyStudent(BrainyStudent brainyStudent)
{
Console.WriteLine("放假了,不开心");
}

public override void VisitInferiorStudent(InferiorStudent inferiorStudent)
{
Console.WriteLine("放假了,开心");
}
}

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
static void Main(string[] args)
{
List<Student> students = new List<Student>
{
new BrainyStudent(),
new InferiorStudent()
//...
};

Visitor v = new ReleaseScoreVisitor();
Visitor v2 = new VacationVisitor();

foreach (var student in students)
{
student.Accept(v);
student.Accept(v2);
}

Console.ReadKey();
}
}

现在要添加访问方法(放假、春游等),就只需要添加访问方式类,而不需要修改写好的类,这样才真正符合开放封闭原则。
在遍历集合时,把访问单个对象的方式抽象出来,这就是访问者模式。
所以说,一定要搞清楚固定的东西是什么,可能会变的东西是什么,才好理解访问者模式的思想。

雇员信息(C#实现)