終于要寫完這個系列了,GOF的設計模式總共有23種,我在前面的篇章只寫了其中16個,剩下的7個放到這篇文章一起寫了。因為這6個設計模式要么是iOS自身語言特性已經實現了,要么是沒有什么太大的利用價值,所以放在一起簡單講解下。
今天要學習如下7種設計模式:
- 原型模式
- 迭代器模式
- 備忘錄模式
- 訪問者模式
- 觀察者模式
- 模板方法模式
- 解釋器模式
下面來一一講解
1、原型模式
定義
用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。
看定義就知道原型模式就是iOS里面的對象克隆,這個iOS已經幫我們做好了,只要我們實現NSCopying或者NSMutableCopying協議就行了,具體實現看這篇文章:
UML結構圖
2、迭代器模式
定義
提供一種方法順序訪問一個聚合對象中各個元素 , 而又不需暴露該對象的內部表示。
簡單來說就是定義一個迭代器,可以使用同一種方式去遍歷不同的的聚合對象,iOS里面的聚合對象就三種:NSArray、NSSet、NSDictonary。其他語言可能還有hash表,map表,鏈表等等iOS同樣已經幫我們實現了迭代器,有三種方式去迭代集合對象
方法1、NSEnumertor抽象類
NSEnumertor本身是一個抽象類,依靠幾個工廠方法來創建并返回具體的迭代器,比如下面的例子使用NSEnumertor來迭代NSArray
NSArray *array = @[@"啦啦啦", @"天空是無用且垂死的星辰", @"人在塔在",@"二營長,你他娘的意大利炮呢"];
NSEnumerator *enumertor = [array objectEnumerator];
id item ;
while (item = [enumertor nextObject]) {
NSLog(@"%@", item);
}
當然還可以使用NSEnumertor來迭代NSSet和NSDictonary,就不在一一演示了,具體看這篇文章:
方法2、基于塊的枚舉
[array enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
}];
這個方法的有點是可以根據條件停止迭代,還可以使用block實現回調
NSArray *array = @[@"啦啦啦", @"天空是無用且垂死的星辰", @"人在塔在",@"二營長,你他娘的意大利炮呢"];
[array enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isEqualToString:@"人在塔在"]){
NSLog(@"%@", obj);
*stop = YES;
}
}];
方法3、快速枚舉
這是蘋果推薦的方法,比for循環效率高,具體使用如下:
NSArray *array = @[@"啦啦啦", @"天空是無用且垂死的星辰", @"人在塔在",@"二營長,你他娘的意大利炮呢"];
for (NSString *str in array) {
NSLog(@"%@", str);
}
UML結構圖
3、備忘錄模式
定義
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。 這樣以后就可將該對象恢復到原先保存的狀態。
簡單來說就是在某個特定的時刻序列化對象,保存到內存或者硬盤上,在需要的時候再回復。保存的一般都是對象的屬性值,比如我們我們打游戲的時候可以保存當前進度,然后可以讀檔,和這個是一樣的道理。
如果是系統的類比如NSArray、NSDictonary,那么iOS系統已經幫我們實現好了,具體使用見這篇文章
objective-C中的序列化(serialize)與反序列化(deserialize)
如果是我們自定義的類需要序列化,那么就在需要序列化的類里面實現NSCoding協議的兩個方法就可以了
- (id)initWithCoder:(NSCoder *)coder
- (void)encodeWithCoder:(NSCoder *)coder
如果覺得一個個寫類的每個屬性非常麻煩,那么就使用如下的宏,一句代碼就可以搞定類的序列化了
#define SERIALIZE_CODER_DECODER() \
\
- (id)initWithCoder:(NSCoder *)coder \
{ \
NSLog(@"%s",__func__); \
Class cls = [self class]; \
while (cls != [NSObject class]) { \
/*判斷是自身類還是父類*/ \
BOOL bIsSelfClass = (cls == [self class]); \
unsigned int iVarCount = 0; \
unsigned int propVarCount = 0; \
unsigned int sharedVarCount = 0; \
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*變量列表,含屬性以及私有變量*/ \
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*屬性列表*/ \
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; \
\
for (int i = 0; i < sharedVarCount; i++) { \
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \
NSString *key = [NSString stringWithUTF8String:varName]; \
id varValue = [coder decodeObjectForKey:key]; \
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
if (varValue && [filters containsObject:key] == NO) { \
[self setValue:varValue forKey:key]; \
} \
} \
free(ivarList); \
free(propList); \
cls = class_getSuperclass(cls); \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)coder \
{ \
NSLog(@"%s",__func__); \
Class cls = [self class]; \
while (cls != [NSObject class]) { \
/*判斷是自身類還是父類*/ \
BOOL bIsSelfClass = (cls == [self class]); \
unsigned int iVarCount = 0; \
unsigned int propVarCount = 0; \
unsigned int sharedVarCount = 0; \
Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*變量列表,含屬性以及私有變量*/ \
objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*屬性列表*/ \
sharedVarCount = bIsSelfClass ? iVarCount : propVarCount; \
\
for (int i = 0; i < sharedVarCount; i++) { \
const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \
NSString *key = [NSString stringWithUTF8String:varName]; \
/*valueForKey只能獲取本類所有變量以及所有層級父類的屬性,不包含任何父類的私有變量(會崩潰)*/ \
id varValue = [self valueForKey:key]; \
NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
if (varValue && [filters containsObject:key] == NO) { \
[coder encodeObject:varValue forKey:key]; \
} \
} \
free(ivarList); \
free(propList); \
cls = class_getSuperclass(cls); \
} \
}
在需要序列化的類里面只需要導入該.h文件,然后寫一句SERIALIZE_CODER_DECODER()
即可
如果需要對序列化后的對象保存到硬盤和從硬盤讀取,下面演示的是保存到NSUserDefault,保存到其他地方操作類似
Myobject *object = [Myobject new];
object.property1 = 賦值1;
object.property2 = 賦值2;
object.property3= 賦值3;
NSData *data1 = [NSKeyedArchiver archivedDataWithRootObject:object];
[[NSUserDefaults standardUserDefaults]setObject:data1 forKey:@"object"];
[[NSUserDefaults standardUserDefaults]synchronize];
讀取
NSData *data = [[NSUserDefaults standardUserDefaults]objectForKey:@"object"];
MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];//反序列化
注意
如果被序列化的對象的屬性是其他類的實例,那么其他類也必須支持序列化,一直遞歸下去。
UML結構圖
4、訪問者模式
定義
表示一個作用于某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提 下定義作用于這些元素的新操作。
假設我們要實現的多個子類擁有相似的功能,我們一般會把這些子類抽象出來一個父類,這樣外界只需要面向抽象父類編程即可。如下圖所示:
而訪問者模式是把這些方法分別實現為一個個的類,然后把這些子類放到一個集合里面,接著對這些子類循環執行這些方法。就是把操作和子類集合分離開來。這樣以后擴展功能,只需要添加一個功能類即可,就不用修改子類,看著很美好是吧。
UML結構圖
問題
但是我想說訪問者模式是所有設計模式里面最沒有用的一個模式,原因如下:
- 對象結構不能改變,因為如果改變對象結構,那么每個concreteVistor都必須增加針對新添加的concreteElement的操作
- 不能對部分concreteVistor操作。因為是循環遍歷所有concreteElement,所以每個concreteElement都必須執行方法,而不能有選擇性的執行方法,而繼承是不會有這種問題。
- 實現復雜。這個模式可以說是設計模式里面實現最為復雜的一種,你看下UML圖就知道了,使用了兩次分發實現回調。
- 每個方法都需要實現為一個concreteVistor。看上面的UML圖,每個concreteVistor都是之前繼承方式的一個方法,然后針對所有的concreteElement分別取實現。假設抽象類有十幾個方法,這不算多吧,那就需要新建十幾個concreteVistor,造成類數目過多,增加程序復雜度
而訪問者模式唯一的優點就是把操作和對象結構分離,可以在不改變對象結構的前提下給對象添加功能。而繼承方式如果要添加功能,就必須給每個子類的添加功能和給父類添加接口,但是對比起來我更愿意使用繼承方式,因為簡單,也不需要增加那么多子類。雖然說使用繼承模式實現違反了開閉原則,但是權衡來看,繼承模式優點更多。
繼承方式對比訪問者模式
當然上面的只是我個人見解,大家自行判斷使用哪種方式好。可以參考下面這個demo,分別用繼承和訪問者模式實現同樣的功能,大家自己體會下。
5、觀察者模式
定義
定義對象間的一種一對多的依賴關系 ,當一個對象的狀態發生改變時 , 所有依賴于它的對象 都得到通知并被自動更新。
這個就不用多說了吧,iOS開發中經常用到的NSNotification
就是這個。如果你有興趣自己實現下該模式,那么下面這個小demo你可以看看。
UML結構圖
6、模板方法模式
定義
定 義 一 個 操 作 中 的 算 法 的 骨 架 , 而 將 一 些 步 驟 延 遲 到 子 類 中 。 可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
這個在日常碼代碼的時候應該是有意識或者無意識的都會有用到,簡單來說就是父類要實現一個很大的功能,需要很多步驟,可以在父類里面把這些步驟抽象出來,成為一個算法骨架。然后子類去具體實現這些步驟。不同的子類可以有不同的實現方式,但是算法結構不改變。直接看圖吧
很簡單對吧,平時開發中應該經常會用到,算是簡單實用的一個設計模式
UML結構圖
7、解釋器模式
定義
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
我現在使用中文寫作,每個句子都需要遵循一定的語法,比如句子大多數由主謂賓構成,然后你看到我寫的文字,你會根據這個規律來解讀這句話的意思。
那么對于計算機來說,要識別一個由特定語法構成的具體,也可以給它指定一套規則讓他解讀,比如正則表達式。關于解釋器的使用就不演示了,這玩意太高大了,一般編譯器才會用到。
有興趣可以看下這篇文章,有演示如何使用解釋器模式
總結
斷斷續續花了2個半月的時間終于把設計模式系列寫完,對自己算是學習的總結,也希望對大家有所幫助,個人感覺學習設計模式還是十分有必要的,如果你使用面向對象語言編程的話(目前流行的語言大多數是面向對象),它可以幫助你打開程序設計世界一扇新的大門。只要多加練習思考,相信你會慢慢領悟到如何構建可復用、靈活、低耦合的程序的。
學習完設計模式只是開端,接下來需要通過大量閱讀優秀的開源項目,并且勤加練習、思考,才能領悟透徹設計模式背后的思想。與君共勉,加油!