设计模式系列15--最终篇

终于要写完这个系列了,GOF的设计模式总共有23种,我在前面的篇章只写了其中16个,剩下的7个放到这篇文章一起写了。因为这6个设计模式要么是iOS自身语言特性已经实现了,要么是没有什么太大的利用价值,所以放在一起简单讲解下。

今天要学习如下7种设计模式:

  1. 原型模式
  2. 迭代器模式
  3. 备忘录模式
  4. 访问者模式
  5. 观察者模式
  6. 模板方法模式
  7. 解释器模式

下面来一一讲解


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文件,然后在.m文件的最末尾写一句SERIALIZE_CODER_DECODER()即可

如果需要对序列化后的对象执行保存到硬盘和从硬盘读取的操作,那么可以存到序列化后存储到数据库或者使用NSUserDefault保存。下面演示的是保存到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个半月的时间终于把设计模式系列写完,对自己算是学习的总结,也希望对大家有所帮助,个人感觉学习设计模式还是十分有必要的,如果你使用面向对象语言编程的话(目前流行的语言大多数是面向对象),它可以帮助你打开程序设计世界一扇新的大门。只要多加练习思考,相信你会慢慢领悟到如何构建可复用、灵活、低耦合的程序的。

学习完设计模式只是开端,接下来需要通过大量阅读优秀的开源项目,并且勤加练习、思考,才能领悟透彻设计模式背后的思想。与君共勉,加油!

comments powered by Disqus