备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象.备忘录模式属于行为型模式
这个模式的思想对大家来说应该很熟悉,很多时候在编程的时候,不小心删了某一段代码或者写错了,咱们都用CMD+Z恢复到原来的状态,那么怎么做到的呢?就是使用备忘录模式,在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
其实很好理解,当你不知道你接下来要做的事情是对是错的时候,那么先把当前做的保存下来,即使下面的你做错了,也可以回到现在的状态,备忘录的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。
模式结构和说明
发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
备忘录: 负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态
管理角色:备忘录管理者,或者称为备忘录负责人。主要负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查。
示例代码
- 创建协议
创建协议的原因是因为备忘录存储的数据不一定是什么类型,我们要让备忘录能存储很多类型的数据,需要让需要备忘的类实现一套统一的接口.
协议包含两个方法,一个获取当前状态的方法,和一个恢复状态的方法.需要使用备忘录的类在这两个方法实现存储和恢复的逻辑
有的人会问,写个基类让被人继承不可以吗?可以,但耦合性会很强
@protocol MementoCenterProtocol <NSObject>
/**
获取状态
*/
- (id)getStatus;
/**
恢复状态
*/
- (void)recoverFromStatus:(id)status;
@end
- 创建备忘录类
备忘录类负责数据的存储和恢复.传过来的object必须是遵循上面协议的对象,这个就可以调用协议里的方法获得需要备忘的数据了
@implementation MementoCenter
+ (void)saveMementoObject:(id<MementoCenterProtocol>)object withKey:(NSString *)key
{
NSParameterAssert(object);
NSParameterAssert(key);
id data = [object getStatus];
NSData *tempData = [FastCoder dataWithRootObject:data];
if (tempData) {
[[NSUserDefaults standardUserDefaults] setObject:tempData forKey:key];
}
}
+ (id)mementoObjectWithKey:(NSString *)key
{
NSParameterAssert(key);
id data = nil;
NSData *tempData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (tempData) {
data = [FastCoder objectWithData:tempData];
}
return data;
}
@end
这里需要注意一下,二进制序列化的时候引入了FastCoder,可以普通对象直接转换成NSData,直接存储,效率高于NSCoding,编码也比NSCoding好用
- 实践(先定义一个NSObject的类,再客户端应用)
@implementation People
- (id)getStatus
{
return @{@"name":self.name,
@"age":@(self.age)
};
}
- (void)recoverFromStatus:(id)status
{
NSDictionary *dic = status;
self.name = dic[@"name"];
self.age = [dic[@"age"] integerValue];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
People *people = [[People alloc] init];
people.name = @"张三";
people.age = 18;
[MementoCenter saveMementoObject:people withKey:@"key"];
people.name = @"李四";
id p = [MementoCenter mementoObjectWithKey:@"key"];
[people recoverFromStatus:p];
NSLog(@"name:%@--age:%ld",people.name,(long)people.age);
}
- 结果
大家可以看出,上面代码修改过people的name值,但恢复数据后,值并没有改变
大家仔细研究一下可以看出,上面的代码有个问题,有人会想很完美啊,完全实现了想要的功能.没错,功能实现没有任何问题,但是你把你实现的细节都暴露出去了,面向对象讲究的是面向接口编程,内部暴露越少越好,何况使用起来也很不方便,如果可以直接使用People对象保存,恢复那多方便啊,那就增加一个NSObject的分类
@implementation NSObject (MementoCenter)
- (void)saveMementoWithKey:(NSString *)key
{
id<MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(getStatus)]) {
[MementoCenter saveMementoObject:obj withKey:key];
}
}
- (void)recoverStatusWithKey:(NSString *)key
{
id status = [MementoCenter mementoObjectWithKey:key];
id <MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(recoverFromStatus:)]) {
[obj recoverFromStatus:status];
}
}
@end
客户端使用:
People *people = [[People alloc] init];
people.name = @"张三";
people.age = 18;
[people saveMementoWithKey:@"key"];
people.name = @"李四";
[people recoverStatusWithKey:@"key"];
现在使用起来是不是很方便了,而且我不用管是谁去保存,恢复的
模式讲解
模式的功能
备忘录的功能,首先是在不破坏封装性的前提下,捕获一个对象的内部状态.这里注意两点,一是不破坏封装性,也就是对象不能暴露它不应该暴露的细节,另外一个是捕获的是对象的内部状态,而且通常运行期间某个时刻对象的内部状态
为什么要捕获这个对象的内部状态呢?捕获这个内部状态有什么用呢?
是要在以后的某个时候,将该对象的状态恢复到备忘录所保存的状态,这才是备忘录真正的目的,前面保存状态就是为了后面恢复,虽然不是一定要恢复,但是目的是为了恢复。这也是很多人理解备忘录模式的时候,忽视掉的地方,他们太关注备忘,而忽视了恢复,这是不全面的理解。
捕获的状态存放在哪里呢?
备忘录模式中,捕获的内部状态,存储在备忘录对象中,而备忘录对象,通常会被存储在原发器对象之外,也就是被保存状态的对象的外部,通常是存放在管理者对象那里
原发器对象
原发器对象,就是需要被保存状态的对象,也是有可能需要恢复状态的对象,原发器一般会包含备忘录对象的实现
通常原发器对象应该提供捕获某个时刻对象内部状态的方法,在这个方法里面,原发器对象会创建备忘录对象,把需要保存的状态数据设置到备忘录对象中,然后把备忘录对象提供给管理者对象来保存。
当然,原发器对象也应该提供这样的方法:按照外部要求来恢复内部状态到某个备忘录对象记录的状态。