一攘轩、概念
1劝评、訪問(wèn)者模式的動(dòng)機(jī)
? 雖然“看病難笋轨,看病貴”了讨,但是一旦身體有恙酌伊,還是要主動(dòng)去醫(yī)院檢查的放棒,不要硬抗死讹。醫(yī)院一般的處理流程:醫(yī)生開(kāi)具處方單推励,劃價(jià)人員拿到處方單之后根據(jù)藥品名稱和數(shù)量計(jì)算總價(jià)悉默,藥房工作人員根據(jù)藥品名稱和數(shù)量準(zhǔn)備藥品城豁。我們可以將處方單看成一個(gè)藥品信息的集合,里面包含了一種或多種不同類型的藥品信息抄课,不同類型的工作人員(如劃價(jià)人員和藥房工作人員)在操作同一個(gè)藥品信息集合時(shí)將提供不同的處理方式唱星,而且可能還會(huì)增加新類型的工作人員來(lái)操作處方單。
? 在軟件開(kāi)發(fā)中跟磨,有時(shí)候我們也需要處理像處方單這樣的集合對(duì)象結(jié)構(gòu)间聊,在該對(duì)象結(jié)構(gòu)中存儲(chǔ)了多個(gè)不同類型的對(duì)象信息,而且對(duì)同一對(duì)象結(jié)構(gòu)中的元素的操作方式并不唯一抵拘,有一種模式可以滿足上述要求哎榴,其模式動(dòng)機(jī)就是以不同的方式操作復(fù)雜對(duì)象結(jié)構(gòu),這就是訪問(wèn)者模式。
2尚蝌、訪問(wèn)者模式的定義
? 訪問(wèn)者模式(Visitor Pattern):提供一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作表示迎变,它使我們可以在不改變各元素的類的前提下定義作用于這些元素的新操作。訪問(wèn)者模式是一種對(duì)象行為型模式飘言。
? 訪問(wèn)者模式是一種較為復(fù)雜的行為型設(shè)計(jì)模式衣形,它包含訪問(wèn)者和被訪問(wèn)元素兩個(gè)主要組成部分。在使用訪問(wèn)者模式時(shí)热凹,被訪問(wèn)元素通常不是單獨(dú)存在的泵喘,它們存儲(chǔ)在一個(gè)集合中,這個(gè)集合被稱為“對(duì)象結(jié)構(gòu)”般妙,訪問(wèn)者通過(guò)遍歷對(duì)象結(jié)構(gòu)實(shí)現(xiàn)對(duì)其中存儲(chǔ)的元素的逐個(gè)操作纪铺。
3、訪問(wèn)者模式的5個(gè)角色
1)Vistor(抽象訪問(wèn)者):抽象訪問(wèn)者為對(duì)象結(jié)構(gòu)中每一個(gè)具體元素類ConcreteElement聲明一個(gè)訪問(wèn)操作碟渺,從這個(gè)操作的名稱或參數(shù)類型可以清楚知道需要訪問(wèn)的具體元素的類型鲜锚,具體訪問(wèn)者需要實(shí)現(xiàn)這些操作方法,定義對(duì)這些元素的訪問(wèn)操作苫拍。
? 這些訪問(wèn)方法的命名一般有兩種方式:一種是直接在方法名中標(biāo)明待訪問(wèn)元素對(duì)象的具體類型芜繁,如visitElementA(ElementA elementA),還有一種是統(tǒng)一取名為visit()绒极,通過(guò)參數(shù)類型的不同來(lái)定義一系列重載的visit()方法骏令。
2)ConcreteVisitor(具體訪問(wèn)者):具體訪問(wèn)者實(shí)現(xiàn)了每個(gè)由抽象訪問(wèn)者聲明的操作,每一個(gè)操作用于訪問(wèn)對(duì)象結(jié)構(gòu)中一種類型的元素垄提。
3)Element(抽象元素):抽象元素一般是抽象類或者接口榔袋,它定義一個(gè)accept()方法,該方法通常以一個(gè)抽象訪問(wèn)者作為參數(shù)铡俐。
4)ConcreteElement(具體元素):具體元素實(shí)現(xiàn)了accept()方法凰兑,在accept()方法中調(diào)用訪問(wèn)者的訪問(wèn)方法以便完成對(duì)一個(gè)元素的操作。
5)ObjectStructure(對(duì)象結(jié)構(gòu)):對(duì)象結(jié)構(gòu)是一個(gè)元素的集合审丘,它用于存放元素對(duì)象吏够,并且提供了遍歷其內(nèi)部元素的方法。它可以結(jié)合組合模式來(lái)實(shí)現(xiàn)滩报,也可以是一個(gè)簡(jiǎn)單的集合對(duì)象锅知,比如一個(gè)List對(duì)象或一個(gè)Set對(duì)象。
4露泊、結(jié)構(gòu)圖
二喉镰、示例
? 本Demo以部門與員工為例:
1)首先創(chuàng)建Employee類,有一個(gè)accept()方法惭笑,表示抽象元素侣姆;
2)然后創(chuàng)建FulltimeEmployee和PartTimeEmployee類生真,繼承自Employee類,實(shí)現(xiàn)accept()方法捺宗,表示具體元素柱蟀;
3)然后創(chuàng)建Department類,有兩個(gè)visit()方法蚜厉,表示抽象訪問(wèn)者长已;
4)然后創(chuàng)建ITDepartment和HRDepartment類,繼承自Department類昼牛,表示具體訪問(wèn)者术瓮;
5)最后創(chuàng)建EmployeeList類,內(nèi)部有個(gè)Array用來(lái)存儲(chǔ)Employee對(duì)象贰健,表示對(duì)象結(jié)構(gòu)胞四。
具體代碼如下:
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; //接受一個(gè)抽象訪問(wèn)者訪問(wèn)
@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]; // 調(diào)用訪問(wèn)者的訪問(wèn)方法
}
@end
// PartTimeEmployee 兼職員工類:具體元素類
@interface PartTimeEmployee : Employee
@end
@implementation PartTimeEmployee
- (void)accept:(Department *)department {
[department visitPartTimeEmployee:self];
}
@end
Department類:
// 部門類:抽象訪問(wèn)者類
@interface Department : NSObject
// OC只有Override重寫,不能Overload重載伶椿,所以這里命名方法不同
// 聲明一組訪問(wèn)方法辜伟,用于訪問(wèn)不同類型的具體元素
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee;
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee;
@end
@implementation Department
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {}
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {}
@end
ITDepartment和HRDepartment類:
// ITDepartment IT部門類:具體訪問(wèn)者類
@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 人力資源部類:具體訪問(wèn)者類
@interface HRDepartment : Department
@end
@implementation HRDepartment
// 實(shí)現(xiàn)人力資源部對(duì)全職員工的訪問(wèn)
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {
NSLog(@"HR部門-全職:姓名%@, %@", employee.name, employee.workTime);
}
// 實(shí)現(xiàn)人力資源部對(duì)兼職員工的訪問(wèn)
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {
NSLog(@"HR部門-兼職:姓名%@, %@", employee.name, employee.workTime);
}
@end
EmployeeList類:
// 員工列表類:對(duì)象結(jié)構(gòu)
@interface EmployeeList : NSObject
- (void)add:(Employee *)employee;
- (void)remove:(Employee *)employee;
- (void)accept:(Department *)department;
@end
@interface EmployeeList ()
@property(nonatomic, strong) NSMutableArray *list; //定義一個(gè)集合用于存儲(chǔ)員工對(duì)象
@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 {
// 遍歷訪問(wèn)員工集合中的每一個(gè)員工對(duì)象
for (Employee *employee in self.list) {
[employee accept:department];
}
}
@end
運(yùn)行代碼:
- (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(@"--------------------------");
// 訪問(wèn)者模式:在系統(tǒng)中增加一種新的訪問(wèn)者,無(wú)須修改源代碼脊另,只要增加一個(gè)新的具體訪問(wèn)者類即可导狡,比如新增HR部門
// 但是如果新增Employee類型,則必定要修改Department偎痛,增加訪問(wèn)新類型的方法
HRDepartment *HR = [HRDepartment new];
[list accept:HR];
}
打印結(jié)果:
IT部門-全職:姓名張三, 朝九晚五
IT部門-兼職:姓名李四, 苦逼的997
IT部門-全職:姓名小紅, 朝九晚五
IT部門-兼職:姓名小麗, 苦逼的996
--------------------------
HR部門-全職:姓名張三, 朝九晚五
HR部門-兼職:姓名李四, 苦逼的997
HR部門-全職:姓名小紅, 朝九晚五
HR部門-兼職:姓名小麗, 苦逼的996
三旱捧、總結(jié)
? 由于訪問(wèn)者模式的使用條件較為苛刻,本身結(jié)構(gòu)也較為復(fù)雜踩麦,因此在實(shí)際應(yīng)用中使用頻率不是特別高廊佩。當(dāng)系統(tǒng)中存在一個(gè)較為復(fù)雜的對(duì)象結(jié)構(gòu),且不同訪問(wèn)者對(duì)其所采取的操作也不相同時(shí)靖榕,可以考慮使用訪問(wèn)者模式進(jìn)行設(shè)計(jì)。在XML文檔解析顽铸、編譯器的設(shè)計(jì)茁计、復(fù)雜集合對(duì)象的處理等領(lǐng)域訪問(wèn)者模式得到了一定的應(yīng)用。
? 訪問(wèn)者模式與抽象工廠模式類似谓松,對(duì)“開(kāi)閉原則”的支持具有傾斜性星压,可以很方便地添加新的訪問(wèn)者,但是添加新的元素較為麻煩鬼譬。
1娜膘、優(yōu)點(diǎn)
1、增加新的訪問(wèn)操作很方便优质。使用訪問(wèn)者模式竣贪,增加新的訪問(wèn)操作就意味著增加一個(gè)新的具體訪問(wèn)者類军洼,實(shí)現(xiàn)簡(jiǎn)單,無(wú)須修改源代碼演怎,符合“開(kāi)閉原則”匕争。
2、將有關(guān)元素對(duì)象的訪問(wèn)行為集中到一個(gè)訪問(wèn)者對(duì)象中爷耀,而不是分散在一個(gè)個(gè)的元素類中甘桑。類的職責(zé)更加清晰,有利于對(duì)象結(jié)構(gòu)中元素對(duì)象的復(fù)用歹叮,相同的對(duì)象結(jié)構(gòu)可以供多個(gè)不同的訪問(wèn)者訪問(wèn)跑杭。
3、讓用戶能夠在不修改現(xiàn)有元素類層次結(jié)構(gòu)的情況下咆耿,定義作用于該層次結(jié)構(gòu)的操作德谅。
2、缺點(diǎn)
1票灰、增加新的元素類很困難女阀。在訪問(wèn)者模式中,每增加一個(gè)新的元素類都意味著要在抽象訪問(wèn)者角色中增加一個(gè)新的抽象操作屑迂,并在每一個(gè)具體訪問(wèn)者類中增加相應(yīng)的具體操作浸策,這違背了“開(kāi)閉原則”的要求。
2惹盼、破壞封裝庸汗。訪問(wèn)者模式要求訪問(wèn)者對(duì)象訪問(wèn)并調(diào)用每一個(gè)元素對(duì)象的操作,這意味著元素對(duì)象有時(shí)候必須暴露一些自己的內(nèi)部操作和內(nèi)部狀態(tài)手报,否則無(wú)法供訪問(wèn)者訪問(wèn)蚯舱。
3、適用場(chǎng)景
1掩蛤、一個(gè)對(duì)象結(jié)構(gòu)包含多個(gè)類型的對(duì)象枉昏,希望對(duì)這些對(duì)象實(shí)施一些依賴其具體類型的操作。在訪問(wèn)者中針對(duì)每一種具體的類型都提供了一個(gè)訪問(wèn)操作揍鸟,不同類型的對(duì)象可以有不同的訪問(wèn)操作兄裂。
2、需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作阳藻,而需要避免讓這些操作“污染”這些對(duì)象的類晰奖,也不希望在增加新操作時(shí)修改這些類。訪問(wèn)者模式使得我們可以將相關(guān)的訪問(wèn)操作集中起來(lái)定義在訪問(wèn)者類中腥泥,對(duì)象結(jié)構(gòu)可以被多個(gè)不同的訪問(wèn)者類所使用匾南,將對(duì)象本身與對(duì)象的訪問(wèn)操作分離。3蛔外、對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變蛆楞,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作溯乒。
Demo地址:iOS-Design-Patterns