一、概念
1、訪問者模式的動機
? 雖然“看病難,看病貴”,但是一旦身體有恙,還是要主動去醫(yī)院檢查的,不要硬抗。醫(yī)院一般的處理流程:醫(yī)生開具處方單,劃價人員拿到處方單之后根據藥品名稱和數量計算總價,藥房工作人員根據藥品名稱和數量準備藥品。我們可以將處方單看成一個藥品信息的集合,里面包含了一種或多種不同類型的藥品信息,不同類型的工作人員(如劃價人員和藥房工作人員)在操作同一個藥品信息集合時將提供不同的處理方式,而且可能還會增加新類型的工作人員來操作處方單。
? 在軟件開發(fā)中,有時候我們也需要處理像處方單這樣的集合對象結構,在該對象結構中存儲了多個不同類型的對象信息,而且對同一對象結構中的元素的操作方式并不唯一,有一種模式可以滿足上述要求,其模式動機就是以不同的方式操作復雜對象結構,這就是訪問者模式。
2、訪問者模式的定義
? 訪問者模式(Visitor Pattern):提供一個作用于某對象結構中的各元素的操作表示,它使我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作。訪問者模式是一種對象行為型模式。
? 訪問者模式是一種較為復雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分。在使用訪問者模式時,被訪問元素通常不是單獨存在的,它們存儲在一個集合中,這個集合被稱為“對象結構”,訪問者通過遍歷對象結構實現對其中存儲的元素的逐個操作。
3、訪問者模式的5個角色
1)Vistor(抽象訪問者):抽象訪問者為對象結構中每一個具體元素類ConcreteElement聲明一個訪問操作,從這個操作的名稱或參數類型可以清楚知道需要訪問的具體元素的類型,具體訪問者需要實現這些操作方法,定義對這些元素的訪問操作。
? 這些訪問方法的命名一般有兩種方式:一種是直接在方法名中標明待訪問元素對象的具體類型,如visitElementA(ElementA elementA),還有一種是統一取名為visit(),通過參數類型的不同來定義一系列重載的visit()方法。
2)ConcreteVisitor(具體訪問者):具體訪問者實現了每個由抽象訪問者聲明的操作,每一個操作用于訪問對象結構中一種類型的元素。
3)Element(抽象元素):抽象元素一般是抽象類或者接口,它定義一個accept()方法,該方法通常以一個抽象訪問者作為參數。
4)ConcreteElement(具體元素):具體元素實現了accept()方法,在accept()方法中調用訪問者的訪問方法以便完成對一個元素的操作。
5)ObjectStructure(對象結構):對象結構是一個元素的集合,它用于存放元素對象,并且提供了遍歷其內部元素的方法。它可以結合組合模式來實現,也可以是一個簡單的集合對象,比如一個List對象或一個Set對象。
4、結構圖

二、示例
? 本Demo以部門與員工為例:
1)首先創(chuàng)建Employee類,有一個accept()方法,表示抽象元素;
2)然后創(chuàng)建FulltimeEmployee和PartTimeEmployee類,繼承自Employee類,實現accept()方法,表示具體元素;
3)然后創(chuàng)建Department類,有兩個visit()方法,表示抽象訪問者;
4)然后創(chuàng)建ITDepartment和HRDepartment類,繼承自Department類,表示具體訪問者;
5)最后創(chuàng)建EmployeeList類,內部有個Array用來存儲Employee對象,表示對象結構。
具體代碼如下:
Employee類:
@class Department;
// 員工類:抽象元素類
@interface Employee : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *workTime;
- (instancetype)initWithName:(NSString *)name workTime:(NSString *)workTime;
- (void)accept:(Department *)department; //接受一個抽象訪問者訪問
@end
@implementation Employee
- (instancetype)initWithName:(NSString *)name workTime:(NSString *)workTime {
self = [super init];
if (self) {
_name = name;
_workTime = workTime;
}
return self;
}
- (void)accept:(Department *)department {}
@end
FulltimeEmployee和PartTimeEmployee類:
// FulltimeEmployee 全職員工類:具體元素類
@interface FulltimeEmployee : Employee
@end
@implementation FulltimeEmployee
- (void)accept:(Department *)department {
[department visitFulltimeEmployee:self]; // 調用訪問者的訪問方法
}
@end
// PartTimeEmployee 兼職員工類:具體元素類
@interface PartTimeEmployee : Employee
@end
@implementation PartTimeEmployee
- (void)accept:(Department *)department {
[department visitPartTimeEmployee:self];
}
@end
Department類:
// 部門類:抽象訪問者類
@interface Department : NSObject
// OC只有Override重寫,不能Overload重載,所以這里命名方法不同
// 聲明一組訪問方法,用于訪問不同類型的具體元素
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee;
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee;
@end
@implementation Department
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {}
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {}
@end
ITDepartment和HRDepartment類:
// ITDepartment IT部門類:具體訪問者類
@interface ITDepartment : Department
@end
@implementation ITDepartment
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {
NSLog(@"IT部門-全職:姓名%@, %@", employee.name, employee.workTime);
}
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {
NSLog(@"IT部門-兼職:姓名%@, %@", employee.name, employee.workTime);
}
@end
// HRDepartment 人力資源部類:具體訪問者類
@interface HRDepartment : Department
@end
@implementation HRDepartment
// 實現人力資源部對全職員工的訪問
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {
NSLog(@"HR部門-全職:姓名%@, %@", employee.name, employee.workTime);
}
// 實現人力資源部對兼職員工的訪問
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {
NSLog(@"HR部門-兼職:姓名%@, %@", employee.name, employee.workTime);
}
@end
EmployeeList類:
// 員工列表類:對象結構
@interface EmployeeList : NSObject
- (void)add:(Employee *)employee;
- (void)remove:(Employee *)employee;
- (void)accept:(Department *)department;
@end
@interface EmployeeList ()
@property(nonatomic, strong) NSMutableArray *list; //定義一個集合用于存儲員工對象
@end
@implementation EmployeeList
- (instancetype)init
{
self = [super init];
if (self) {
_list = [NSMutableArray array];
}
return self;
}
- (void)add:(Employee *)employee {
[self.list addObject:employee];
}
- (void)remove:(Employee *)employee {
if ([self.list containsObject:employee]) {
[self.list removeObject:employee];
}
}
- (void)accept:(Department *)department {
// 遍歷訪問員工集合中的每一個員工對象
for (Employee *employee in self.list) {
[employee accept:department];
}
}
@end
運行代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Employee *zhangSan = [[FulltimeEmployee alloc] initWithName:@"張三" workTime:@"朝九晚五"];
Employee *liSi = [[PartTimeEmployee alloc] initWithName:@"李四" workTime:@"苦逼的997"];
Employee *xiaoHong = [[FulltimeEmployee alloc] initWithName:@"小紅" workTime:@"朝九晚五"];
Employee *xiaoLi = [[PartTimeEmployee alloc] initWithName:@"小麗" workTime:@"苦逼的996"];
EmployeeList *list = [EmployeeList new];
[list add:zhangSan];
[list add:liSi];
[list add:xiaoHong];
[list add:xiaoLi];
ITDepartment *IT = [ITDepartment new];
[list accept:IT];
NSLog(@"--------------------------");
// 訪問者模式:在系統中增加一種新的訪問者,無須修改源代碼,只要增加一個新的具體訪問者類即可,比如新增HR部門
// 但是如果新增Employee類型,則必定要修改Department,增加訪問新類型的方法
HRDepartment *HR = [HRDepartment new];
[list accept:HR];
}
打印結果:
IT部門-全職:姓名張三, 朝九晚五
IT部門-兼職:姓名李四, 苦逼的997
IT部門-全職:姓名小紅, 朝九晚五
IT部門-兼職:姓名小麗, 苦逼的996
--------------------------
HR部門-全職:姓名張三, 朝九晚五
HR部門-兼職:姓名李四, 苦逼的997
HR部門-全職:姓名小紅, 朝九晚五
HR部門-兼職:姓名小麗, 苦逼的996
三、總結
? 由于訪問者模式的使用條件較為苛刻,本身結構也較為復雜,因此在實際應用中使用頻率不是特別高。當系統中存在一個較為復雜的對象結構,且不同訪問者對其所采取的操作也不相同時,可以考慮使用訪問者模式進行設計。在XML文檔解析、編譯器的設計、復雜集合對象的處理等領域訪問者模式得到了一定的應用。
? 訪問者模式與抽象工廠模式類似,對“開閉原則”的支持具有傾斜性,可以很方便地添加新的訪問者,但是添加新的元素較為麻煩。
1、優(yōu)點
1、增加新的訪問操作很方便。使用訪問者模式,增加新的訪問操作就意味著增加一個新的具體訪問者類,實現簡單,無須修改源代碼,符合“開閉原則”。
2、將有關元素對象的訪問行為集中到一個訪問者對象中,而不是分散在一個個的元素類中。類的職責更加清晰,有利于對象結構中元素對象的復用,相同的對象結構可以供多個不同的訪問者訪問。
3、讓用戶能夠在不修改現有元素類層次結構的情況下,定義作用于該層次結構的操作。
2、缺點
1、增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作,并在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”的要求。
2、破壞封裝。訪問者模式要求訪問者對象訪問并調用每一個元素對象的操作,這意味著元素對象有時候必須暴露一些自己的內部操作和內部狀態(tài),否則無法供訪問者訪問。
3、適用場景
1、一個對象結構包含多個類型的對象,希望對這些對象實施一些依賴其具體類型的操作。在訪問者中針對每一種具體的類型都提供了一個訪問操作,不同類型的對象可以有不同的訪問操作。
2、需要對一個對象結構中的對象進行很多不同的并且不相關的操作,而需要避免讓這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類。訪問者模式使得我們可以將相關的訪問操作集中起來定義在訪問者類中,對象結構可以被多個不同的訪問者類所使用,將對象本身與對象的訪問操作分離。3、對象結構中對象對應的類很少改變,但經常需要在此對象結構上定義新的操作。
Demo地址:iOS-Design-Patterns