终于要写完这个系列了,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个半月的时间终于把设计模式系列写完,对自己算是学习的总结,也希望对大家有所帮助,个人感觉学习设计模式还是十分有必要的,如果你使用面向对象语言编程的话(目前流行的语言大多数是面向对象),它可以帮助你打开程序设计世界一扇新的大门。只要多加练习思考,相信你会慢慢领悟到如何构建可复用、灵活、低耦合的程序的。
学习完设计模式只是开端,接下来需要通过大量阅读优秀的开源项目,并且勤加练习、思考,才能领悟透彻设计模式背后的思想。与君共勉,加油!