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

西木柚子發表於2016-12-24

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

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

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

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

下面來一一講解


1、原型模式

定義

用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

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

深淺拷貝

UML結構圖

設計模式系列 15-- 最終篇
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結構圖

設計模式系列 15-- 最終篇
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結構圖

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


4、訪問者模式

定義

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

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

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

而訪問者模式是把這些方法分別實現為一個個的類,然後把這些子類放到一個集合裡面,接著對這些子類迴圈執行這些方法。就是把操作和子類集合分離開來。這樣以後擴充套件功能,只需要新增一個功能類即可,就不用修改子類,看著很美好是吧。

UML結構圖

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

問題

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

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

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

繼承方式對比訪問者模式

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

訪問者模式VS繼承模式Demo


5、觀察者模式

定義

定義物件間的一種一對多的依賴關係 ,當一個物件的狀態發生改變時 , 所有依賴於它的物件 都得到通知並被自動更新。

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

觀察者模式Demo

UML結構圖

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


6、模板方法模式

定義

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

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

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

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

UML結構圖

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


7、直譯器模式

定義

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

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

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

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

直譯器模式


總結

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

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

相關文章