iOS 設(shè)計(jì)模式之二十四(訪問(wèn)者模式)

一攘轩、概念

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)圖
訪問(wèn)者模式

二喉镰、示例

? 本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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市臊岸,隨后出現(xiàn)的幾起案子橙数,更是在濱河造成了極大的恐慌,老刑警劉巖帅戒,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灯帮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逻住,警方通過(guò)查閱死者的電腦和手機(jī)钟哥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瞎访,“玉大人腻贰,你說(shuō)我怎么就攤上這事“墙眨” “怎么了播演?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)伴奥。 經(jīng)常有香客問(wèn)我写烤,道長(zhǎng),這世上最難降的妖魔是什么拾徙? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任洲炊,我火速辦了婚禮,結(jié)果婚禮上尼啡,老公的妹妹穿的比我還像新娘暂衡。我一直安慰自己,他們只是感情好崖瞭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布狂巢。 她就那樣靜靜地躺著,像睡著了一般书聚。 火紅的嫁衣襯著肌膚如雪隧膘。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天寺惫,我揣著相機(jī)與錄音,去河邊找鬼蹦疑。 笑死西雀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歉摧。 我是一名探鬼主播艇肴,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腔呜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了再悼?” 一聲冷哼從身側(cè)響起核畴,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冲九,沒(méi)想到半個(gè)月后谤草,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莺奸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年丑孩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灭贷。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡温学,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甚疟,到底是詐尸還是另有隱情仗岖,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布览妖,位于F島的核電站轧拄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏黄痪。R本人自食惡果不足惜紧帕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桅打。 院中可真熱鬧是嗜,春花似錦、人聲如沸挺尾。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遭铺。三九已至丽柿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間魂挂,已是汗流浹背甫题。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涂召,地道東北人坠非。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像果正,于是被迫代替她去往敵國(guó)和親炎码。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盟迟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容