原則一钙姊、單一職責(zé)原則(Single Responsibility Principle毯辅,簡(jiǎn)稱(chēng)SRP )
定義:應(yīng)該有且僅有一個(gè)原因引起類(lèi)的變更。
一個(gè)類(lèi)只負(fù)責(zé)一項(xiàng)職責(zé)煞额,如果發(fā)生變更時(shí)思恐,可以考慮將一個(gè)類(lèi)拆分成兩個(gè)類(lèi),或者在一個(gè)類(lèi)中添加新的方法立镶。
在真實(shí)的開(kāi)發(fā)中壁袄,不僅僅是類(lèi)类早、函數(shù)和接口也要遵循單一職責(zé)原則媚媒。即:一個(gè)函數(shù)負(fù)責(zé)一個(gè)功能。如果一個(gè)函數(shù)里面有不同的功能涩僻,則需要將不同的功能的函數(shù)分離出去缭召。
優(yōu)點(diǎn):
- 類(lèi)的復(fù)雜性降低,實(shí)現(xiàn)什么職責(zé)都有清晰明確的定義逆日。
- 類(lèi)的可讀性提高嵌巷,復(fù)雜性減低。
如果接口或者函數(shù)的單一職責(zé)做得好室抽,一個(gè)接口或者函數(shù)的修改只對(duì)相應(yīng)的類(lèi)有影響搪哪,對(duì)其他接口或者函數(shù)無(wú)影響,這對(duì)系統(tǒng)的擴(kuò)展性坪圾、維護(hù)性都有非常大的幫助晓折。
例如,需求上指出用一個(gè)類(lèi)描述食肉和食草動(dòng)物:
//================== Animal.h ==================
@interface Animal : NSObject
- (void)eatWithAnimalName:(NSString *)animalName;
@end
運(yùn)行結(jié)果:
2018-10-27 17:55:25.775317+0800 DesignPatterns[54087:24701786] 狼 吃肉
2018-10-27 17:55:25.775689+0800 DesignPatterns[54087:24701786] 豹 吃肉
2018-10-27 17:55:25.775721+0800 DesignPatterns[54087:24701786] 虎 吃肉
上線(xiàn)后兽泄,發(fā)現(xiàn)問(wèn)題了漓概,并不是所有的動(dòng)物都是吃肉的,比如羊就是吃草的病梢。修改時(shí)如果遵循單一職責(zé)原則胃珍,需要將 Animal
類(lèi)細(xì)分為食草動(dòng)物類(lèi) Herbivore
,食肉動(dòng)物 Carnivore
蜓陌,代碼如下:
//================== Herbivore.h ==================
@interface Herbivore : Animal
@end
@implementation Herbivore
- (void)eatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃草", animalName);
}
@end
//================== Carnivore.h ==================
@interface Carnivore : Animal
@end
@implementation Carnivore
- (void)eatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃肉", animalName);
}
@end
//================== main 函數(shù) ==================
Animal *carnivore = [Carnivore new];
[carnivore eatWithAnimalName:@"狼"];
[carnivore eatWithAnimalName:@"豹"];
[carnivore eatWithAnimalName:@"虎"];
NSLog(@"\n");
Animal *herbivore = [Herbivore new];
[herbivore eatWithAnimalName:@"羊"];
在子類(lèi)里面重寫(xiě)父類(lèi)的 eatWithAnimalName
函數(shù)觅彰,運(yùn)行結(jié)果:
2018-10-27 18:04:49.189722+0800 DesignPatterns[54422:24725132] 狼 吃肉
2018-10-27 18:04:49.190450+0800 DesignPatterns[54422:24725132] 豹 吃肉
2018-10-27 18:04:49.190482+0800 DesignPatterns[54422:24725132] 虎 吃肉
2018-10-27 18:04:49.190498+0800 DesignPatterns[54422:24725132]
2018-10-27 18:04:49.190530+0800 DesignPatterns[54422:24725132] 羊 吃草
這樣一來(lái),不僅僅在此次新需求中滿(mǎn)足了單一職責(zé)原則钮热,以后如果還要增加食肉動(dòng)物和食草動(dòng)物的其他功能缔莲,就可以直接在這兩個(gè)類(lèi)里面添加即可。但是霉旗,有一點(diǎn)痴奏,修改花銷(xiāo)是很大的蛀骇,除了將原來(lái)的類(lèi)分解之外,還需要修改 main
函數(shù) 读拆。而直接修改類(lèi) Animal
來(lái)達(dá)成目的雖然違背了單一職責(zé)原則擅憔,但花銷(xiāo)卻小的多,代碼如下:
//================== Animal.h ==================
@interface Animal : NSObject
- (void)eatWithAnimalName:(NSString *)animalName;
@end
@implementation Animal
- (void)eatWithAnimalName:(NSString *)animalName {
if ([@"羊" isEqualToString:animalName]) {
NSLog(@"%@ 吃草", animalName);
} else {
NSLog(@"%@ 吃肉", animalName);
}
}
@end
//================== main 函數(shù) ==================
Animal *animal = [Animal new];
[animal eatWithAnimalName:@"狼"];
[animal eatWithAnimalName:@"豹"];
[animal eatWithAnimalName:@"虎"];
[animal eatWithAnimalName:@"羊"];
運(yùn)行結(jié)果:
2018-10-27 18:16:10.910397+0800 DesignPatterns[54677:24751636] 狼 吃肉
2018-10-27 18:16:10.911105+0800 DesignPatterns[54677:24751636] 豹 吃肉
2018-10-27 18:16:10.911138+0800 DesignPatterns[54677:24751636] 虎 吃肉
2018-10-27 18:16:10.911160+0800 DesignPatterns[54677:24751636] 羊 吃草
可以看到檐晕,這種修改方式要簡(jiǎn)單的多暑诸。
但是卻存在著隱患:有一天需求上增加牛和馬也需要吃草,則又需要修改 Animal
類(lèi)的 eatWithAnimalName
函數(shù)辟灰,而對(duì)原有代碼的修改會(huì)對(duì)調(diào)用狼个榕、豹和虎吃肉等功能帶來(lái)風(fēng)險(xiǎn),也許某一天你會(huì)發(fā)現(xiàn)運(yùn)行結(jié)果變?yōu)榛⒁渤圆萘恕?/strong>這種修改方式直接在代碼級(jí)別上違背了單一職責(zé)原則芥喇,雖然修改起來(lái)最簡(jiǎn)單西采,但隱患卻是最大的。還有一種修改方式:
//================== Animal.h ==================
@interface Animal : NSObject
/**
* 吃草
*/
- (void)eatGrassWithAnimalName:(NSString *)animalName;
/**
* 吃肉
*/
- (void)eatMeatWithAnimalName:(NSString *)animalName;
@end
@implementation Animal
- (void)eatGrassWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃草", animalName);
}
- (void)eatMeatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃肉", animalName);
}
@end
//================== main 函數(shù) ==================
Animal *animal = [Animal new];
[animal eatMeatWithAnimalName:@"狼"];
[animal eatMeatWithAnimalName:@"豹"];
[animal eatMeatWithAnimalName:@"虎"];
[animal eatGrassWithAnimalName:@"羊"];
運(yùn)行結(jié)果:
2018-10-27 18:31:30.321473+0800 DesignPatterns[55048:24787008] 狼 吃肉
2018-10-27 18:31:30.321884+0800 DesignPatterns[55048:24787008] 豹 吃肉
2018-10-27 18:31:30.321922+0800 DesignPatterns[55048:24787008] 虎 吃肉
2018-10-27 18:31:30.321939+0800 DesignPatterns[55048:24787008] 羊 吃草
通過(guò)運(yùn)行結(jié)果可以看到继控,這種修改方式?jīng)]有改動(dòng)原來(lái)的函數(shù)械馆,而是在類(lèi)中新加了一個(gè)函數(shù),這樣雖然也違背了類(lèi)單一職責(zé)原則武通,但在函數(shù)級(jí)別上卻是符合單一職責(zé)原則的霹崎,因?yàn)樗](méi)有動(dòng)原來(lái)函數(shù)的代碼。
在實(shí)際的開(kāi)發(fā)應(yīng)用中冶忱,有很多復(fù)雜的場(chǎng)景尾菇,怎么設(shè)計(jì)一個(gè)類(lèi)或者一個(gè)函數(shù),讓?xiě)?yīng)用程序更加靈活囚枪,是更多程序員們值得思考的派诬,需要結(jié)合特定的需求場(chǎng)景,有可能有些類(lèi)里面有很多的功能眶拉,但是切記不要將不屬于這個(gè)類(lèi)本身的功能也強(qiáng)加進(jìn)來(lái)千埃,這樣不僅帶來(lái)不必要的維護(hù)成本,也違反了單一職責(zé)的設(shè)計(jì)原則忆植。
原則二放可、里氏替換原則(Liskov Substitution Principle,簡(jiǎn)稱(chēng)LSP)
定義:如果對(duì)一個(gè)類(lèi)型為 T1
的對(duì)象 o1
朝刊,都有類(lèi)型為 T2
的對(duì)象 o2
耀里,使得以 T1
定義的所有程序 P
在所有的對(duì)象 o1
都替換成 o2
時(shí),程序 P
的行為沒(méi)有發(fā)生變化拾氓,那么類(lèi)型 T2
是類(lèi)型 T1
的子類(lèi)型冯挎。有點(diǎn)拗口,通俗點(diǎn)講咙鞍,只要父類(lèi)能出現(xiàn)的地方子類(lèi)就可以出現(xiàn)房官,而且替換為子類(lèi)也不會(huì)產(chǎn)生任何錯(cuò)誤或異常趾徽,使用者不需要知道是父類(lèi)還是子類(lèi)。但是翰守,反過(guò)來(lái)就不行了孵奶,有子類(lèi)出現(xiàn)的地方,父類(lèi)未必就能適應(yīng)蜡峰。
面向?qū)ο蟮恼Z(yǔ)言的三大特點(diǎn)是繼承了袁、封裝、多態(tài)湿颅,里氏替換原則就是依賴(lài)于繼承载绿、多態(tài)這兩大特性。當(dāng)使用繼承時(shí)油航,遵循里氏替換原則崭庸。但是使用繼承會(huì)給程序帶來(lái)侵入性,程序的可移植性降低劝堪,增加了對(duì)象間的耦合性冀自,如果一個(gè)類(lèi)被其他的類(lèi)所繼承揉稚,則當(dāng)這個(gè)類(lèi)需要修改時(shí)秒啦,必須考慮到所有的子類(lèi),并且父類(lèi)修改后搀玖,所有涉及到子類(lèi)的功能都有可能會(huì)產(chǎn)生影響余境。子類(lèi)可以擴(kuò)展父類(lèi)的功能,但不能改變父類(lèi)原有的功能灌诅。
注意:
- 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法芳来,但不能覆蓋父類(lèi)的非抽象方法。
- 子類(lèi)中可以增加自己特有的方法猜拾。
- 當(dāng)子類(lèi)的方法重載父類(lèi)的方法時(shí)即舌,方法的前置條件(即方法的形參)要比父類(lèi)方法的輸入?yún)?shù)更寬松。
- 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的抽象方法時(shí)挎袜,方法的后置條件(即方法的返回值)要比父類(lèi)更嚴(yán)格顽聂。
比如,需要完成一個(gè)兩數(shù)相加的功能:
//================== A.h ==================
@interface A : NSObject
/**
加法
@param a
@param b
@return 相加之后的和
*/
- (NSInteger)addition:(NSInteger)a b:(NSInteger)b;
@end
//================== main 函數(shù) ==================
A *a = [[A alloc] init];
NSLog(@"100+50=%ld", [a addition:100 b:50]);
NSLog(@"100+80=%ld", [a addition:100 b:80]);
運(yùn)行結(jié)果如下盯仪,
2018-11-01 22:53:23.549358+0800 DesignPatterns[18063:363232] 100+50=150
2018-11-01 22:53:23.549586+0800 DesignPatterns[18063:363232] 100+80=180
接著紊搪,需求上需要增加一個(gè)新的功能,完成兩數(shù)相加全景,然后再與 100
求差耀石,由類(lèi) B
來(lái)負(fù)責(zé)。即類(lèi) B
需要完成兩個(gè)功能:
- 兩數(shù)相減爸黄。
- 兩數(shù)相加滞伟,然后再加
100
揭鳞。
由于類(lèi) A
已經(jīng)實(shí)現(xiàn)了加法功能,所以 B
繼承 A
之后梆奈,只需要完成減法功能就可以了汹桦,但是在類(lèi) B
中不小心重寫(xiě)了父類(lèi) A
的減法功能,如下:
//================== B.h ==================
@interface B : A
/**
加法
@param a
@param b
@return 相加之后的和
*/
- (NSInteger)addition:(NSInteger)a b:(NSInteger)b;
/**
減法
@param a
@param b
@return 相加之后的和
*/
- (NSInteger)subtraction:(NSInteger)a b:(NSInteger)b;
@end
//================== main 函數(shù) ==================
B *b = [[B alloc] init];
NSInteger sub = [b addition:100 b:50];
NSInteger difference = [b subtraction:sub b:100];
NSLog(@"100+50=%ld", sub);
NSLog(@"100+100+50=%ld", difference);
運(yùn)行結(jié)果如下鉴裹,
2018-11-01 23:15:06.530080+0800 DesignPatterns[18363:375940] 100+50=5000
2018-11-01 23:15:06.530758+0800 DesignPatterns[18363:375940] 100+100+50=4900
發(fā)現(xiàn)原本運(yùn)行正常的相減功能發(fā)生了錯(cuò)誤舞骆,原因就是類(lèi) B
在給方法起名時(shí)無(wú)意中重寫(xiě)了父類(lèi)的方法,造成所有運(yùn)行相減功能的代碼全部調(diào)用了類(lèi) B
重寫(xiě)后的方法径荔,造成原本運(yùn)行正常的功能出現(xiàn)了錯(cuò)誤督禽。如果按照“里氏替換原則”,只要父類(lèi)能出現(xiàn)的地方子類(lèi)就可以出現(xiàn)总处,而且替換為子類(lèi)也不會(huì)產(chǎn)生任何錯(cuò)誤或異常狈惫,使用者不需要知道是父類(lèi)還是子類(lèi),是不成立的鹦马。
在平時(shí)的日常開(kāi)發(fā)中胧谈,通常會(huì)通過(guò)重寫(xiě)父類(lèi)的方法來(lái)完成新的功能,這樣寫(xiě)起來(lái)雖然簡(jiǎn)單荸频,但是整個(gè)繼承體系的可復(fù)用性會(huì)比較差菱肖,特別是運(yùn)用多態(tài)比較頻繁時(shí),程序運(yùn)行出錯(cuò)的幾率非常大旭从。
原則三稳强、依賴(lài)倒置原則(Dependence Inversion Principle,簡(jiǎn)稱(chēng)DIP)
依賴(lài)倒置原則的核心思想是面向接口編程和悦。
定義:模塊間的依賴(lài)通過(guò)抽象發(fā)生退疫,高層模塊和低層模塊之間不應(yīng)該發(fā)生直接的依賴(lài)關(guān)系,二者都應(yīng)該是通過(guò)接口或抽象類(lèi)產(chǎn)生的鸽素;即依賴(lài)抽象褒繁,而不依賴(lài)具體的實(shí)現(xiàn)。
例如:類(lèi) A
直接依賴(lài)類(lèi) B
馍忽,假如要將類(lèi) A
改為依賴(lài)類(lèi) C
棒坏,則必須通過(guò)修改類(lèi) A
的代碼來(lái)達(dá)成。比如在這種場(chǎng)景下舵匾,業(yè)務(wù)邏輯層類(lèi) A
相對(duì)于數(shù)據(jù)層類(lèi) B
是高層模塊俊抵,因?yàn)闃I(yè)務(wù)邏輯層需要調(diào)用數(shù)據(jù)層去連接數(shù)據(jù)庫(kù),如果業(yè)務(wù)邏輯層類(lèi) A
依賴(lài)數(shù)據(jù)層類(lèi) B
的話(huà)坐梯,那么將來(lái)需求變更徽诲,需要把舊的數(shù)據(jù)層類(lèi) B
修改為新的數(shù)據(jù)層類(lèi) C
,就必須通過(guò)修改類(lèi) A
,這樣就會(huì)給應(yīng)用程序帶來(lái)不必要的風(fēng)險(xiǎn)谎替。
解決方案:將類(lèi) A
修改為依賴(lài)接口 I
偷溺,類(lèi) B
和類(lèi) C
各自實(shí)現(xiàn)接口 I
,類(lèi) A
通過(guò)接口 I
間接與類(lèi) B
或者類(lèi) C
發(fā)生聯(lián)系钱贯,則會(huì)大大降低修改類(lèi) A
的幾率挫掏。要做到可擴(kuò)展高復(fù)用,盡量不要讓業(yè)務(wù)邏輯層依賴(lài)數(shù)據(jù)層秩命,可以在數(shù)據(jù)層抽象出一個(gè)接口尉共,讓業(yè)務(wù)邏輯層依賴(lài)于這個(gè)抽象接口。
比如:母親給孩子講故事弃锐,只要給她一本書(shū)袄友,她就可以照著書(shū)給孩子講故事了。
//================== Book.h ==================
@interface Book : NSObject
/**
故事內(nèi)容
*/
- (void)theStoryContent;
@end
//================== Mother.h ==================
@class Book;
@interface Mother : NSObject
/**
講故事
*/
- (void)tellStory:(Book *)book;
@end
//================== main 函數(shù) ==================
Mother *mother = [Mother new];
Book *book = [Book new];
[mother tellStory:book];
運(yùn)行結(jié)果如下霹菊,
2018-11-09 14:52:08.759154+0800 DesignPatterns[6135:458778] 媽媽開(kāi)始講故事
2018-11-09 14:52:08.759365+0800 DesignPatterns[6135:458778] 很久很久以前有一個(gè)阿拉伯的故事……
將來(lái)有一天剧蚣,需求變更成,增加讓母親講一下報(bào)紙上的故事的功能旋廷,如下:
//================== Newspaper.h ==================
@interface Newspaper : NSObject
/**
報(bào)紙內(nèi)容
*/
- (void)theStoryContent;
@end
如果將 Newspaper
類(lèi)替換 Book
類(lèi)鸠按,發(fā)現(xiàn)母親看不懂報(bào)紙上的故事,必須要修改 Mother
類(lèi)里面的 tellStory
方法才能看不懂報(bào)紙上的故事饶碘。假如以后需求換成雜志呢目尖?換成網(wǎng)頁(yè)呢?還要不斷地修改Mother
類(lèi)熊镣,這顯然不是好的設(shè)計(jì)卑雁,高層模塊都依賴(lài)了低層模塊的改動(dòng)募书,因此上述設(shè)計(jì)不符合依賴(lài)倒置原則绪囱。Mother
類(lèi)與 Book
類(lèi)之間的耦合性太高了,必須降低他們之間的耦合度才行莹捡。
解決方案鬼吵,將母親講故事的方法抽象一個(gè)接口或者 Protocol
,讓Mother
類(lèi)不再依賴(lài) Newspaper
和 Book
類(lèi)具體實(shí)現(xiàn)篮赢,而是依賴(lài)抽象出來(lái)的接口或者 Protocol
齿椅。并且 Newspaper
和 Book
類(lèi)也都依賴(lài)這個(gè)抽象出來(lái)的接口或者 Protocol
,通過(guò)實(shí)現(xiàn)接口或者 Protocol
來(lái)做自己的事情启泣。
//================== IReaderProtocol.h ==================
@protocol IReaderProtocol <NSObject>
/**
故事內(nèi)容
*/
- (void)theStoryContent;
@end
Mother
類(lèi)與接口 IReader
發(fā)生依賴(lài)關(guān)系涣脚,而 Book
和 Newspaper
都屬于讀物的范疇,他們各自都去實(shí)現(xiàn) IReader
接口寥茫,這樣就符合依賴(lài)倒置原則了遣蚀,代碼修改為:
//================== Book.h ==================
@interface Book : NSObject <IReaderProtocol>
@end
//================== Newspaper.h ==================
@interface Newspaper : NSObject <IReaderProtocol>
@end
//================== IReaderProtocol.h ==================
@protocol IReaderProtocol <NSObject>
/**
故事內(nèi)容
*/
- (void)theStoryContent;
@end
//================== Mother.h ==================
@interface Mother : NSObject
/**
講故事
*/
- (void)tellStory:(NSObject<IReaderProtocol> *)reading;
@end
@implementation Mother
- (void)tellStory:(NSObject<IReaderProtocol> *)reading {
NSLog(@"媽媽開(kāi)始講故事");
if ([reading respondsToSelector:@selector(theStoryContent)]) {
[reading theStoryContent];
}
}
@end
//================== main 函數(shù) ==================
Mother *mother = [Mother new];
Book *book = [Book new];
Newspaper *newspaper = [Newspaper new];
[mother tellStory:book];
[mother tellStory:newspaper];
運(yùn)行結(jié)果如下,
2018-11-09 15:28:01.182603+0800 DesignPatterns[7055:532924] 媽媽開(kāi)始講故事
2018-11-09 15:28:01.182879+0800 DesignPatterns[7055:532924] 很久很久以前有一個(gè)阿拉伯的故事……
2018-11-09 15:28:01.182916+0800 DesignPatterns[7055:532924] 媽媽開(kāi)始講故事
2018-11-09 15:28:01.182955+0800 DesignPatterns[7055:532924] 雄鹿終結(jié)勇士八連勝……
這樣修改后,無(wú)論以后怎樣擴(kuò)展 main 函數(shù)芭梯,都不需要再修改 Mother
類(lèi)了险耀。這里只是舉了一個(gè)比較簡(jiǎn)單的例子,在實(shí)際的項(xiàng)目開(kāi)發(fā)中玖喘,盡可能的采用“低耦合甩牺,高內(nèi)聚”的原則,采用依賴(lài)倒置原則給多人并行開(kāi)發(fā)帶來(lái)了極大的便利累奈,無(wú)論是面向過(guò)程編程還是面向?qū)ο缶幊瘫崤桑挥惺垢鱾€(gè)模塊之間的耦合盡量的低,才能提高代碼的復(fù)用率澎媒。所以遵循依賴(lài)倒置原則可以降低類(lèi)之間的耦合性赠群,提高系統(tǒng)的穩(wěn)定性,降低修改程序造成的風(fēng)險(xiǎn)旱幼。
原則四查描、接口隔離原則(Interface Segregation Principle,簡(jiǎn)稱(chēng)ISP)
定義:客戶(hù)端不應(yīng)該依賴(lài)它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(lài)應(yīng)該建立在最小的接口上柏卤。
Class 'ClassB' does not conform to protocol 'InterfaceH'
Class 'ClassD' does not conform to protocol 'InterfaceH'
注意:在 Objective-C
中的協(xié)議可以通過(guò) @optional
關(guān)鍵字聲明不需要必須實(shí)現(xiàn)的方法冬三,這個(gè)只是 Objective-C
的一個(gè)特性,可以消除在 ClassB
和 ClassD
中沒(méi)有實(shí)現(xiàn) InterfaceH
的 protocol
協(xié)議缘缚。
比如勾笆,類(lèi) A 依賴(lài)接口 H 中的方法1、方法2桥滨、方法5窝爪,類(lèi) B 是對(duì)類(lèi) A 依賴(lài)的實(shí)現(xiàn)。類(lèi) C 依賴(lài)接口 H 中的方法3齐媒、方法4蒲每、方法5,類(lèi) D 是對(duì)類(lèi) C 依賴(lài)的實(shí)現(xiàn)喻括。對(duì)于類(lèi) B 和類(lèi) D 來(lái)說(shuō)邀杏,雖然他們都存在著用不到的方法,但由于實(shí)現(xiàn)了接口 H唬血,因?yàn)榻涌?H
對(duì)于類(lèi) A
和類(lèi) C
來(lái)說(shuō)不是最小接口望蜡,所以也必須要實(shí)現(xiàn)這些用不到的方法。
//================== InterfaceH.h ==================
@protocol InterfaceH <NSObject>
- (void)method1;
- (void)method2;
- (void)method3;
- (void)method4;
- (void)method5;
@end
//================== ClassB.h ==================
@interface ClassB : NSObject <InterfaceH>
@end
@implementation ClassB
- (void)method1 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法1");
}
- (void)method2 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法2");
}
- (void)method3 {
//not necessarily
}
- (void)method4 {
//not necessarily
}
- (void)method5 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法5");
}
@end
//================== ClassA.h ==================
@interface ClassA : NSObject
- (void)depend:(NSObject<InterfaceH> *)classB;
@end
@implementation ClassA
- (void)depend:(NSObject<InterfaceH> *)classB {
if ([classB respondsToSelector:@selector(method1)]) {
[classB method1];
}
if ([classB respondsToSelector:@selector(method2)]) {
[classB method2];
}
if ([classB respondsToSelector:@selector(method5)]) {
[classB method5];
}
}
@end
//================== ClassD.h ==================
@interface ClassD : NSObject <InterfaceH>
@end
@implementation ClassD
- (void)method1 {
//not necessarily
}
- (void)method2 {
//not necessarily
}
- (void)method3 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法3");
}
- (void)method4 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法4");
}
- (void)method5 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法5");
}
@end
//================== ClassC.h ==================
@interface ClassC : NSObject
- (void)depend:(NSObject<InterfaceH> *)classD;
@end
@implementation ClassC
- (void)depend:(NSObject<InterfaceH> *)classD {
if ([classD respondsToSelector:@selector(method3)]) {
[classD method3];
}
if ([classD respondsToSelector:@selector(method4)]) {
[classD method4];
}
if ([classD respondsToSelector:@selector(method5)]) {
[classD method5];
}
}
@end
可以看到拷恨,如果接口過(guò)于臃腫脖律,只要接口中出現(xiàn)的方法,不管對(duì)依賴(lài)于它的類(lèi)有沒(méi)有用處腕侄,實(shí)現(xiàn)類(lèi)中都必須去實(shí)現(xiàn)這些方法小泉,這顯然不是好的設(shè)計(jì)勒叠。由于接口方法的設(shè)計(jì)造成了冗余,因此該設(shè)計(jì)不符合接口隔離原則膏孟。
解決方法:將臃腫的接口 H
拆分為獨(dú)立的幾個(gè)接口眯分,類(lèi) A
和類(lèi) C
分別與他們需要的接口建立依賴(lài)關(guān)系,也就是采用接口隔離原則柒桑。
//================== InterfaceH.h ==================
@protocol InterfaceH <NSObject>
- (void)method5;
@end
@protocol InterfaceH1 <InterfaceH>
- (void)method1;
- (void)method2;
@end
@protocol InterfaceH2 <InterfaceH>
- (void)method3;
- (void)method4;
@end
//================== ClassB.h ==================
@interface ClassB : NSObject <InterfaceH1>
@end
@implementation ClassB
- (void)method1 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法1");
}
- (void)method2 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法2");
}
- (void)method5 {
NSLog(@"類(lèi) B 實(shí)現(xiàn)接口 H 的方法5");
}
@end
//================== ClassA.h ==================
@interface ClassA : NSObject
- (void)depend:(NSObject<InterfaceH1> *)classB;
@end
@implementation ClassA
- (void)depend:(NSObject<InterfaceH1> *)classB {
if ([classB respondsToSelector:@selector(method1)]) {
[classB method1];
}
if ([classB respondsToSelector:@selector(method2)]) {
[classB method2];
}
if ([classB respondsToSelector:@selector(method5)]) {
[classB method5];
}
}
@end
//================== ClassD.h ==================
@interface ClassD : NSObject <InterfaceH2>
@end
@implementation ClassD
- (void)method3 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法3");
}
- (void)method4 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法4");
}
- (void)method5 {
NSLog(@"類(lèi) D 實(shí)現(xiàn)接口 H 的方法5");
}
@end
//================== ClassC.h ==================
@interface ClassC : NSObject
- (void)depend:(NSObject<InterfaceH2> *)classD;
@end
@implementation ClassC
- (void)depend:(NSObject<InterfaceH2> *)classD {
if ([classD respondsToSelector:@selector(method3)]) {
[classD method3];
}
if ([classD respondsToSelector:@selector(method4)]) {
[classD method4];
}
if ([classD respondsToSelector:@selector(method5)]) {
[classD method5];
}
}
@end
接口隔離原則的含義是:建立單一接口弊决,不要建立龐大臃腫的接口,盡量細(xì)化接口魁淳,接口中的方法盡量少飘诗。在實(shí)際項(xiàng)目開(kāi)發(fā)中,只暴露給調(diào)用的類(lèi)需要的方法界逛,不需要的方法則隱藏起來(lái)昆稿。只有專(zhuān)注地為一個(gè)模塊提供定制服務(wù),才能建立最小的依賴(lài)關(guān)系息拜,不要試圖去建立一個(gè)很龐大的接口供所有依賴(lài)它的類(lèi)去調(diào)用溉潭。通過(guò)分散定義多個(gè)接口,可以預(yù)防外來(lái)變更的擴(kuò)散少欺,提高系統(tǒng)的靈活性和可維護(hù)性喳瓣。
原則五、迪米特法則(Law of Demeter赞别,簡(jiǎn)稱(chēng)LOD)
定義:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解畏陕。
當(dāng)類(lèi)與類(lèi)之間的關(guān)系越密切,耦合度越大仿滔,當(dāng)一個(gè)類(lèi)發(fā)生改變時(shí)惠毁,對(duì)另一個(gè)類(lèi)的影響也越大。通俗的來(lái)講崎页,就是一個(gè)類(lèi)對(duì)自己依賴(lài)的類(lèi)知道的越少越好鞠绰。也就是說(shuō),對(duì)于被依賴(lài)的類(lèi)來(lái)說(shuō)实昨,無(wú)論邏輯多么復(fù)雜洞豁,都盡量地的將邏輯封裝在類(lèi)的內(nèi)部,對(duì)外只暴露必要的接口荒给。
解決方案:盡量降低類(lèi)與類(lèi)之間的耦合。
比如刁卜,有一個(gè)集團(tuán)公司志电,下屬單位有分公司和直屬部門(mén),現(xiàn)在要求打印出所有下屬單位的員工 ID
:
Model 類(lèi)蛔趴,
//================== EmployeeModel.h ==================
@interface EmployeeModel : NSObject
/**
總公司員工ID
*/
@property (nonatomic, copy) NSString *employee_id;
@end
//================== SubEmployeeModel.h ==================
@interface SubEmployeeModel : NSObject
/**
分公司員工ID
*/
@property (nonatomic, copy) NSString *subemployee_id;
@end
Company 類(lèi)挑辆,
//================== Company.h ==================
@interface Company : NSObject
- (NSArray *)getAllEmployee;
- (void)printAllEmployeeWithSubCompany:(SubCompany *)subCompany;
@end
@implementation Company
- (NSArray *)getAllEmployee {
NSMutableArray<EmployeeModel *> *employeeArray = [NSMutableArray<EmployeeModel *> array];
for (int i = 0; i < 3; i++) {
EmployeeModel *employeeModel = [[EmployeeModel alloc] init];
[employeeModel setEmployee_id:[@(i) stringValue]];
[employeeArray addObject:employeeModel];
}
return employeeArray.copy;
}
- (void)printAllEmployeeWithSubCompany:(SubCompany *)subCompany {
// 分公司員工
NSArray<SubEmployeeModel *> *subEmployeeArray = subCompany.getAllEmployee;
for (SubEmployeeModel *employeeModel in subEmployeeArray) {
NSLog(@"分公司員工ID:%@", employeeModel.subemployee_id);
}
// 總公司員工
NSArray<EmployeeModel *> *employeeArray = self.getAllEmployee;
for (EmployeeModel *employeeModel in employeeArray) {
NSLog(@"總公司員工ID:%@", employeeModel.employee_id);
}
}
@end
//================== SubCompany.h ==================
@interface SubCompany : NSObject
- (NSArray *)getAllEmployee;
@end
@implementation SubCompany
- (NSArray *)getAllEmployee {
NSMutableArray<SubEmployeeModel *> *employeeArray = [NSMutableArray<SubEmployeeModel *> array];
for (int i = 0; i < 3; i++) {
SubEmployeeModel *employeeModel = [[SubEmployeeModel alloc] init];
[employeeModel setSubemployee_id:[@(i) stringValue]];
[employeeArray addObject:employeeModel];
}
return employeeArray.copy;
}
@end
從上面可以看出,打印 Company
所有員工的 ID
,需要依賴(lài)分公司 SubCompany
鱼蝉。但是在 printAllEmployeeWithSubCompany:
方法里面必須要初始化分公司員工 SubEmployeeModel
洒嗤。而SubEmployeeModel
和 Company
并不是直接聯(lián)系,換句話(huà)說(shuō)魁亦,總公司 Company
只需要依賴(lài)分公司 SubCompany
渔隶,與分公司的員工 SubEmployeeModel
并沒(méi)有任何聯(lián)系,這樣設(shè)計(jì)顯然是增加了不必要的耦合洁奈。
按照迪米特法則间唉,類(lèi)與類(lèi)之間的應(yīng)該減少不必要的關(guān)聯(lián)程度。
//================== Company.h ==================
@interface Company : NSObject
/**
獲取所有分公司員工
*/
- (NSArray *)getAllEmployee;
/**
打印公司所有員工
*/
- (void)printAllEmployeeWithSubCompany:(SubCompany *)subCompany;
@end
@implementation Company
- (NSArray *)getAllEmployee {
NSMutableArray<EmployeeModel *> *employeeArray = [NSMutableArray<EmployeeModel *> array];
for (int i = 0; i < 3; i++) {
EmployeeModel *employeeModel = [[EmployeeModel alloc] init];
[employeeModel setEmployee_id:[@(i) stringValue]];
[employeeArray addObject:employeeModel];
}
return employeeArray.copy;
}
- (void)printAllEmployeeWithSubCompany:(SubCompany *)subCompany {
// 分公司員工
[subCompany printAllEmployee];
// 總公司員工
NSArray<EmployeeModel *> *employeeArray = self.getAllEmployee;
for (EmployeeModel *employeeModel in employeeArray) {
NSLog(@"總公司員工ID:%@", employeeModel.employee_id);
}
}
@end
//================== SubCompany.h ==================
@interface SubCompany : NSObject
/**
獲取所有分公司員工
*/
- (NSArray *)getAllEmployee;
/**
打印分公司所有員工
*/
- (void)printAllEmployee;
@end
@implementation SubCompany
- (NSArray *)getAllEmployee {
NSMutableArray<SubEmployeeModel *> *employeeArray = [NSMutableArray<SubEmployeeModel *> array];
for (int i = 0; i < 3; i++) {
SubEmployeeModel *employeeModel = [[SubEmployeeModel alloc] init];
[employeeModel setSubemployee_id:[@(i) stringValue]];
[employeeArray addObject:employeeModel];
}
return employeeArray.copy;
}
- (void)printAllEmployee {
// 分公司員工
NSArray<SubEmployeeModel *> *subEmployeeArray = self.getAllEmployee;
for (SubEmployeeModel *employeeModel in subEmployeeArray) {
NSLog(@"分公司員工ID:%@", employeeModel.subemployee_id);
}
}
@end
修改后利术,為分公司增加了打印所有公鑰 ID
的方法呈野,總公司直接調(diào)分公司的打印方法,從而避免了與分公司的員工發(fā)生耦合印叁。
耦合的方式很多被冒,依賴(lài)、關(guān)聯(lián)轮蜕、組合姆打、聚合等。
迪米特法則的初衷是降低類(lèi)之間的耦合肠虽,由于每個(gè)類(lèi)都減少了不必要的依賴(lài)幔戏,因此的確可以降低耦合關(guān)系。但是過(guò)分的使用迪米特原則税课,會(huì)產(chǎn)生大量傳遞類(lèi)闲延,導(dǎo)致系統(tǒng)復(fù)雜度變大。所以在采用迪米特法則時(shí)要反復(fù)權(quán)衡韩玩,既做到結(jié)構(gòu)清晰垒玲,又要高內(nèi)聚低耦合。
原則六找颓、開(kāi)閉原則(Open Close Principle合愈,簡(jiǎn)稱(chēng)OCP)
定義:一個(gè)軟件實(shí)體如類(lèi)、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開(kāi)放击狮,對(duì)修改關(guān)閉佛析。
核心思想:盡量通過(guò)擴(kuò)展應(yīng)用程序中的類(lèi)、模塊和函數(shù)來(lái)解決不同的需求場(chǎng)景彪蓬,而不是通過(guò)直接修改已有的類(lèi)寸莫、模塊和函數(shù)。
用抽象構(gòu)建框架档冬,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié)膘茎,對(duì)擴(kuò)展開(kāi)放的關(guān)鍵是抽象桃纯,而對(duì)象的多態(tài)則保證了這種擴(kuò)展的開(kāi)放性。開(kāi)放原則首先意味著我們可以自由地增加功能披坏,而不會(huì)影響原有功能态坦。這就要求我們能夠通過(guò)繼承完成功能的擴(kuò)展。其次棒拂,開(kāi)放原則還意味著實(shí)現(xiàn)是可替換的伞梯。只有利用抽象,才可以為定義提供不同的實(shí)現(xiàn)着茸,然后根據(jù)不同的需求實(shí)例化不同的實(shí)現(xiàn)子類(lèi)壮锻。
開(kāi)放封閉原則的優(yōu)點(diǎn):
- 代碼可讀性高,可維護(hù)性強(qiáng)涮阔。
- 幫助縮小邏輯粒度猜绣,以提高可復(fù)用性。
- 可以使維護(hù)人員只擴(kuò)展一個(gè)類(lèi)掰邢,而非修改一個(gè)類(lèi),從而提高可維護(hù)性伟阔。
- 在設(shè)計(jì)之初考慮所有可能變化的因素辣之,留下接口,從而符合面向?qū)ο箝_(kāi)發(fā)的要求皱炉。
比如怀估,書(shū)店售書(shū)的經(jīng)典例子:
//================== IBookProtocol.h ==================
@protocol IBookProtocol <NSObject>
/**
獲取書(shū)籍名稱(chēng)
*/
- (NSString *)bookName;
/**
獲取書(shū)籍售價(jià)
*/
- (CGFloat)bookPrice;
/**
獲取書(shū)籍作者
*/
- (NSString *)bookAuthor;
@end
//================== NovelBook.h ==================
@interface NovelBook : NSObject <IBookProtocol>
- (instancetype)initWithBookName:(NSString *)name
price:(CGFloat)price
author:(NSString *)author;
@end
//================== BookStore.h ==================
@interface BookStore : NSObject
- (NSArray<IBookProtocol> *)bookArray;
@end
//================== main 函數(shù) ==================
// 模擬書(shū)店賣(mài)書(shū)
BookStore *bookStore = [BookStore new];
for (NovelBook *novelBook in bookStore.bookArray) {
NSLog(@"書(shū)籍名稱(chēng):%@ 書(shū)籍作者:%@ 書(shū)籍價(jià)格:%2f", [novelBook bookName], [novelBook bookAuthor], [novelBook bookPrice]);
}
運(yùn)行結(jié)果如下,
2018-11-12 15:11:32.642070+0800 DesignPatterns[1863:5763476] 書(shū)籍名稱(chēng):天龍八部 書(shū)籍作者:金庸 書(shū)籍價(jià)格:50.000000
2018-11-12 15:11:32.642495+0800 DesignPatterns[1863:5763476] 書(shū)籍名稱(chēng):巴黎圣母院 書(shū)籍作者:雨果 書(shū)籍價(jià)格:70.000000
2018-11-12 15:11:32.642530+0800 DesignPatterns[1863:5763476] 書(shū)籍名稱(chēng):悲慘世界 書(shū)籍作者:雨果 書(shū)籍價(jià)格:80.000000
2018-11-12 15:11:32.642558+0800 DesignPatterns[1863:5763476] 書(shū)籍名稱(chēng):金瓶梅 書(shū)籍作者:蘭陵王 書(shū)籍價(jià)格:40.000000
將來(lái)某一天需求變更為項(xiàng)目投產(chǎn)合搅,書(shū)店盈利多搀,書(shū)店決定灾部,40
元以上打 8
折康铭,40
元以下打 9
折。
在實(shí)際的項(xiàng)目開(kāi)發(fā)中赌髓,如果不懂得開(kāi)閉原則的話(huà)从藤,很容易犯下面的錯(cuò)誤:
- 在
IBookProtocol
上新增加一個(gè)方法bookOffPrice()
方法,專(zhuān)門(mén)進(jìn)行打折锁蠕,所有實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)這個(gè)方法夷野,但是如果其他不想打折的書(shū)籍也會(huì)因?yàn)閷?shí)現(xiàn)了書(shū)籍的接口必須打折。 - 修改
NovelBook
實(shí)現(xiàn)類(lèi)中的bookPrice()
方中實(shí)現(xiàn)打折處理匿沛,由于該方法已經(jīng)實(shí)現(xiàn)了打折處理價(jià)格扫责,因此采購(gòu)書(shū)籍人員看到的也是打折后的價(jià)格的情況。
很顯然按照上面兩種方案的話(huà)逃呼,隨著需求的增加鳖孤,需要反復(fù)修改之前創(chuàng)建的類(lèi),給新增的類(lèi)造成了不必要的冗余抡笼,業(yè)務(wù)邏輯的處理和需求不相符合等情況苏揣。
//================== OffNovelBook.h ==================
@interface OffNovelBook : NovelBook
@end
@implementation OffNovelBook
- (instancetype)initWithBookName:(NSString *)name
price:(CGFloat)price
author:(NSString *)author {
return [super initWithBookName:name price:price author:author];
}
- (CGFloat)bookPrice {
CGFloat originalPrice = [super bookPrice];
CGFloat offPrice = 0;
if (originalPrice > 40) {
offPrice = originalPrice * 0.8;
} else {
offPrice = originalPrice * 0.9;
}
return offPrice;
}
@end
//================== BookStore.h ==================
@interface BookStore : NSObject
- (NSArray<IBookProtocol> *)bookArray;
- (NSArray<IBookProtocol> *)offBookArray;
@end
@implementation BookStore
- (NSArray<IBookProtocol> *)bookArray {
NSMutableArray<IBookProtocol> *tempArray = [NSMutableArray<IBookProtocol> array];
NovelBook *book1 = [[NovelBook alloc] initWithBookName:@"天龍八部" price:30 author:@"金庸"];
[tempArray addObject:book1];
NovelBook *book2 = [[NovelBook alloc] initWithBookName:@"巴黎圣母院" price:70 author:@"雨果"];
[tempArray addObject:book2];
NovelBook *book3 = [[NovelBook alloc] initWithBookName:@"悲慘世界" price:80 author:@"雨果"];
[tempArray addObject:book3];
NovelBook *book4 = [[NovelBook alloc] initWithBookName:@"金瓶梅" price:40 author:@"蘭陵王"];
[tempArray addObject:book4];
return tempArray;
}
- (NSArray<IBookProtocol> *)offBookArray {
NSMutableArray<IBookProtocol> *tempArray = [NSMutableArray<IBookProtocol> array];
OffNovelBook *book1 = [[OffNovelBook alloc] initWithBookName:@"天龍八部" price:30 author:@"金庸"];
[tempArray addObject:book1];
OffNovelBook *book2 = [[OffNovelBook alloc] initWithBookName:@"巴黎圣母院" price:70 author:@"雨果"];
[tempArray addObject:book2];
OffNovelBook *book3 = [[OffNovelBook alloc] initWithBookName:@"悲慘世界" price:80 author:@"雨果"];
[tempArray addObject:book3];
OffNovelBook *book4 = [[OffNovelBook alloc] initWithBookName:@"金瓶梅" price:40 author:@"蘭陵王"];
[tempArray addObject:book4];
return tempArray;
}
@end
//================== main 函數(shù) ==================
BookStore *bookStore = [BookStore new];
NSLog(@"------------書(shū)店賣(mài)出去的原價(jià)書(shū)籍記錄如下:------------");
for (NovelBook *novelBook in bookStore.bookArray) {
NSLog(@"書(shū)籍名稱(chēng):%@ 書(shū)籍作者:%@ 書(shū)籍價(jià)格:%2f", [novelBook bookName], [novelBook bookAuthor], [novelBook bookPrice]);
}
NSLog(@"------------書(shū)店賣(mài)出去的打折書(shū)籍記錄如下:------------");
for (OffNovelBook *novelBook in bookStore.offBookArray) {
NSLog(@"書(shū)籍名稱(chēng):%@ 書(shū)籍作者:%@ 書(shū)籍價(jià)格:%2f", [novelBook bookName], [novelBook bookAuthor], [novelBook bookPrice]);
}
運(yùn)行結(jié)果如下,
2018-11-12 15:52:01.639550+0800 DesignPatterns[2962:6151804] ------------書(shū)店賣(mài)出去的原價(jià)書(shū)籍記錄如下:------------
2018-11-12 15:52:01.639895+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):天龍八部 書(shū)籍作者:金庸 書(shū)籍價(jià)格:30.000000
2018-11-12 15:52:01.639927+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):巴黎圣母院 書(shū)籍作者:雨果 書(shū)籍價(jià)格:70.000000
2018-11-12 15:52:01.639951+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):悲慘世界 書(shū)籍作者:雨果 書(shū)籍價(jià)格:80.000000
2018-11-12 15:52:01.639971+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):金瓶梅 書(shū)籍作者:蘭陵王 書(shū)籍價(jià)格:40.000000
2018-11-12 15:52:01.639988+0800 DesignPatterns[2962:6151804] ------------書(shū)店賣(mài)出去的打折書(shū)籍記錄如下:------------
2018-11-12 15:52:01.640029+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):天龍八部 書(shū)籍作者:金庸 書(shū)籍價(jià)格:27.000000
2018-11-12 15:52:01.640145+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):巴黎圣母院 書(shū)籍作者:雨果 書(shū)籍價(jià)格:56.000000
2018-11-12 15:52:01.640194+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):悲慘世界 書(shū)籍作者:雨果 書(shū)籍價(jià)格:64.000000
2018-11-12 15:52:01.640217+0800 DesignPatterns[2962:6151804] 書(shū)籍名稱(chēng):金瓶梅 書(shū)籍作者:蘭陵王 書(shū)籍價(jià)格:36.000000
在實(shí)際的項(xiàng)目開(kāi)發(fā)中推姻,
對(duì)抽象定義的修改平匈,要保證定義的接口或者
Protocol
的穩(wěn)定轻抱,尤其要保證被其他對(duì)象調(diào)用的接口的穩(wěn)定蝇恶;否則俏讹,就會(huì)導(dǎo)致修改蔓延赤套,牽一發(fā)而動(dòng)全身官卡。對(duì)具體實(shí)現(xiàn)的修改枚粘,因?yàn)榫唧w實(shí)現(xiàn)的修改户敬,可能會(huì)給調(diào)用者帶來(lái)意想不到的結(jié)果唉擂。如果確實(shí)需要修改具體的實(shí)現(xiàn)厂捞,就需要做好達(dá)到測(cè)試覆蓋率要求的單元測(cè)試输玷。