設計模式系列15--最終篇

image

終于要寫完這個系列了,GOF的設計模式總共有23種,我在前面的篇章只寫了其中16個,剩下的7個放到這篇文章一起寫了。因為這6個設計模式要么是iOS自身語言特性已經實現了,要么是沒有什么太大的利用價值,所以放在一起簡單講解下。

今天要學習如下7種設計模式:

  1. 原型模式
  1. 迭代器模式
  2. 備忘錄模式
  3. 訪問者模式
  4. 觀察者模式
  5. 模板方法模式
  6. 解釋器模式

下面來一一講解


1、原型模式

定義

用原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象。

看定義就知道原型模式就是iOS里面的對象克隆,這個iOS已經幫我們做好了,只要我們實現NSCopying或者NSMutableCopying協議就行了,具體實現看這篇文章:

深淺拷貝

UML結構圖

image

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,就不在一一演示了,具體看這篇文章:

NSEnumertor迭代器的使用

方法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結構圖

image

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結構圖

image

4、訪問者模式

定義

表示一個作用于某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提 下定義作用于這些元素的新操作。

假設我們要實現的多個子類擁有相似的功能,我們一般會把這些子類抽象出來一個父類,這樣外界只需要面向抽象父類編程即可。如下圖所示:

image

而訪問者模式是把這些方法分別實現為一個個的類,然后把這些子類放到一個集合里面,接著對這些子類循環執行這些方法。就是把操作和子類集合分離開來。這樣以后擴展功能,只需要添加一個功能類即可,就不用修改子類,看著很美好是吧。

UML結構圖

image

問題

但是我想說訪問者模式是所有設計模式里面最沒有用的一個模式,原因如下:

  • 對象結構不能改變,因為如果改變對象結構,那么每個concreteVistor都必須增加針對新添加的concreteElement的操作
  • 不能對部分concreteVistor操作。因為是循環遍歷所有concreteElement,所以每個concreteElement都必須執行方法,而不能有選擇性的執行方法,而繼承是不會有這種問題。
  • 實現復雜。這個模式可以說是設計模式里面實現最為復雜的一種,你看下UML圖就知道了,使用了兩次分發實現回調。
  • 每個方法都需要實現為一個concreteVistor。看上面的UML圖,每個concreteVistor都是之前繼承方式的一個方法,然后針對所有的concreteElement分別取實現。假設抽象類有十幾個方法,這不算多吧,那就需要新建十幾個concreteVistor,造成類數目過多,增加程序復雜度

而訪問者模式唯一的優點就是把操作和對象結構分離,可以在不改變對象結構的前提下給對象添加功能。而繼承方式如果要添加功能,就必須給每個子類的添加功能和給父類添加接口,但是對比起來我更愿意使用繼承方式,因為簡單,也不需要增加那么多子類。雖然說使用繼承模式實現違反了開閉原則,但是權衡來看,繼承模式優點更多。

繼承方式對比訪問者模式

當然上面的只是我個人見解,大家自行判斷使用哪種方式好。可以參考下面這個demo,分別用繼承和訪問者模式實現同樣的功能,大家自己體會下。

訪問者模式VS繼承模式Demo


5、觀察者模式

定義

定義對象間的一種一對多的依賴關系 ,當一個對象的狀態發生改變時 , 所有依賴于它的對象 都得到通知并被自動更新。

這個就不用多說了吧,iOS開發中經常用到的NSNotification就是這個。如果你有興趣自己實現下該模式,那么下面這個小demo你可以看看。

觀察者模式Demo

UML結構圖

image

6、模板方法模式

定義

定 義 一 個 操 作 中 的 算 法 的 骨 架 , 而 將 一 些 步 驟 延 遲 到 子 類 中 。 可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

這個在日常碼代碼的時候應該是有意識或者無意識的都會有用到,簡單來說就是父類要實現一個很大的功能,需要很多步驟,可以在父類里面把這些步驟抽象出來,成為一個算法骨架。然后子類去具體實現這些步驟。不同的子類可以有不同的實現方式,但是算法結構不改變。直接看圖吧

image

很簡單對吧,平時開發中應該經常會用到,算是簡單實用的一個設計模式

UML結構圖

image

7、解釋器模式

定義

給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。

我現在使用中文寫作,每個句子都需要遵循一定的語法,比如句子大多數由主謂賓構成,然后你看到我寫的文字,你會根據這個規律來解讀這句話的意思。

那么對于計算機來說,要識別一個由特定語法構成的具體,也可以給它指定一套規則讓他解讀,比如正則表達式。關于解釋器的使用就不演示了,這玩意太高大了,一般編譯器才會用到。

有興趣可以看下這篇文章,有演示如何使用解釋器模式

解釋器模式


總結

斷斷續續花了2個半月的時間終于把設計模式系列寫完,對自己算是學習的總結,也希望對大家有所幫助,個人感覺學習設計模式還是十分有必要的,如果你使用面向對象語言編程的話(目前流行的語言大多數是面向對象),它可以幫助你打開程序設計世界一扇新的大門。只要多加練習思考,相信你會慢慢領悟到如何構建可復用、靈活、低耦合的程序的。

學習完設計模式只是開端,接下來需要通過大量閱讀優秀的開源項目,并且勤加練習、思考,才能領悟透徹設計模式背后的思想。與君共勉,加油!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容