(-) 提出問題
我們在編程的時候,經常需要保存對象的一個狀態,當需要的時候,可以恢復到這個狀態。比如,我們使用Xcode進行編程時,假如編寫失誤(例如不小心誤刪除了幾行代碼),我們希望返回刪除前的狀態,便可以使用Ctrl+Z來進行返回。這時我們便可以使用備忘錄模式來實現。
(二 )備忘錄設計模式 簡介
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,這樣以后就可以將對象恢復到原先保存的的狀態。——《設計模式》
通用類圖如下:
使用場景:
當滿足以下兩個條件的時候,應當考慮使用這一模式。
1、需要保存一個對象(活某部分)在某一時刻的狀態,這樣以后就可以恢復先前的狀態;
2、用于獲取狀態的接口,會暴露實現的細節,需要將其隱藏起來;
(二 )使用備忘錄設計模式
通過一個簡單的例子來實現這個設計模式。例子很簡單,只是完成一個類的一個屬性保存和恢復。
第1步 創建協議
創建協議的原因是因為備忘錄存儲的數據不一定是什么類型的,我們要讓備忘錄能存儲很多類型的數據,需要讓需要備忘的類實現一套統一的接口。
協議包含兩個方法,一個獲取當前狀態的方法,和一個恢復狀態的方法。需要使用備忘錄的類在這兩個方法里實現存儲和恢復的邏輯。
#import <Foundation/Foundation.h>
@protocol MementoCenterProtocol <NSObject>
/*
獲得狀態
*/
-(id)getStatus;
/*
設置狀態
*/
-(void)setStatus:(id)data;
@end
第2步 創建備忘錄類
備忘錄中心類只負責數據的存儲和恢復。傳過來的object必須是繼承上面協議的對象,這樣就可以調用協議里的方法獲得需要備忘的數據了。類里有兩個方法。
根據key保存狀態
/**
* 保存備忘錄
*
* @param object object
* @param key key
*/
+(void)saveMementoObject:(id<MementoCenterProtocol>)object withKey:(NSString*)key{
if (object==nil || key==nil) {
return;
}
id data = [object getStatus];
NSData *tmpData = [FastCoder dataWithRootObject:data];
// 進行存儲
if (tmpData) {
[[NSUserDefaults standardUserDefaults] setObject:tmpData
forKey:key];
}
}
根據key獲得object狀態數據
/**
* 獲得保存的狀態數據
*
* @param key key
*
* @return 狀態
*/
+(id)getMementObjectWithKey:(NSString*)key{
if (key==nil) {
return nil;
}
id data = nil;
NSData *tmpData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if (tmpData) {
data = [FastCoder objectWithData:tmpData];
}
return data;
}
這兩個方法里面用到了一個類FastCoder的方法,FastCoder是一個Cocoa object和object graph的高性能二進制序列化格式,可以作為Property Lists和JSON的替代選擇。FastCoder git地址。
第3步 創建工具類
如果我們直接調用備忘錄的方法的話,會暴露出我們實現的細節,也會暴露出我們要備忘的object。所以我們創建一個工具類,來再封裝一層。這樣我們就不會暴露具體的實現細節了。
/**
* 備忘狀態
*
* @param key 鍵值
*/
- (void)saveStateWithKey:(NSString *)key;
/**
* 恢復狀態
*
* @param key 鍵值
*/
- (void)recoverFromStateWithKey:(NSString *)key;
實現:
- (void)saveStateWithKey:(NSString *)key{
if (key==nil) {
return;
}
id<MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(getStatus)]) {
[MementoCenter saveMementoObject:obj withKey:key];
}
}
- (void)recoverFromStateWithKey:(NSString *)key{
if (key==nil) {
return;
}
id state = [MementoCenter getMementObjectWithKey:key];
id<MementoCenterProtocol> obj = (id<MementoCenterProtocol>)self;
if ([obj respondsToSelector:@selector(setStatus:)]) {
[obj setStatus:state];
}
}
第4步 創建測試類
測試類里只有一個字符串屬性,我們來恢復這個屬性。測試類要實現上面協議的方法。
-(id)getStatus{
return self.valueStr.length? self.valueStr: @"當前運動:10.00KM";
}
-(void)setStatus:(id)data{
if (data) {
self.valueStr = (NSString*)data;
}
}
第5步 測試
TestView *texeView = [[TestView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:texeView];
texeView.valueStr = @"idage";
NSLog(@"恢復前 ===%@",texeView.valueStr );
//保存
[texeView saveStateWithKey:@"test"];
texeView.valueStr = @"idage163";
//恢復
[texeView recoverFromStateWithKey:@"test"];
NSLog(@"恢復后 ===%@",texeView.valueStr );
這樣我們就是實現了既能保存和恢復狀態,也不會暴露保存和恢復的細節。
(四)鳴謝
這篇文章是看過 YouXianMing老師 的教程后,得到的啟發并重構自己項目代碼后寫的總結。希望對大家有點幫助。在此感謝YouXianMing老師。