设计模式之访问者模式

定义:
提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。

实例:
在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为「对象结构」,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。通过一个简单的例子了解访问者模式,访问者有财务部门FADepartment和 HR 部门HRDepartment,通过访问雇员Employee来查看雇员的工作情况。

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
// 部门抽象类 - 访问者抽象类
@interface Department : NSObject
// 访问抽象方法 用来声明方法
- (void)visitEmployee:(Employee *)employee;

@end

@implementation Department

- (void)visitEmployee:(Employee *)employee {}

@end

// 财务部门 - 具体访问者类
@interface FADepartment : Department
@end

@implementation FADepartment
// 访问具体方法
- (void)visitEmployee:(Employee *)employee {
if (employee.workTime > 40) {
NSLog(@"%@ 工作时间满 40 小时", employee.name);
}else{
NSLog(@"%@ 工作时间不满 40 小时,要警告!", employee.name);
}
}

@end

// HR 部门 - 具体访问者类
@interface HRDepartment : Department
@end

@implementation HRDepartment
// 访问具体方法
- (void)visitEmployee:(Employee *)employee {
NSUInteger weekSalary = employee.workTime * employee.salary;
NSLog(@"%@ 本周获取薪资:%ld",employee.name , weekSalary);
}

@end

// 抽象雇员类 - 被访问者抽象类
@interface Employee : NSObject
// 姓名
@property (nonatomic, strong) NSString *name;
// 工作时间
@property (nonatomic, assign) NSUInteger workTime;
// 时薪
@property (nonatomic, assign) NSUInteger salary;
// 接受访问抽象方法
- (void)accept:(Department *)department;

@end

@implementation Employee

- (void)accept:(Department *)department {}

@end

// 雇员具体类 - 被访问者具体类
@interface FulltimeEmployee : Employee

@end

@implementation FulltimeEmployee
// 接受访问具体方法
- (void)accept:(Department *)department {
[department visitEmployee:self];
}

@end

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
// 新建财务和 HR - 访问者
FADepartment *fa = [FADepartment new];
HRDepartment *hr = [HRDepartment new];

// 新建雇员 - 被访问者
FulltimeEmployee *tim = [FulltimeEmployee new];
tim.name = @"tim";
tim.workTime = 55;
tim.salary = 100;

FulltimeEmployee *bill = [FulltimeEmployee new];
bill.name = @"bill";
bill.workTime = 38;
bill.salary = 150;

// 一般被访问者都存储在数据集合中方便遍历,集合中可以存储不同类型的被访问者
NSArray *employeeList = @[tim, bill];
[employeeList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
Employee *employee = obj;
// 接受财务访问
[employee accept:fa];
// 接受 HR 访问
[employee accept:hr];
}];

输出:
tim 工作时间满 40 小时
tim 本周获取薪资:5500
bill 工作时间不满 40 小时,要警告!
bill 本周获取薪资:5700

优点:

  • 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
  • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
  • 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

缺点:

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
  • 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。