设计模式系列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阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容