一靠粪、概念
1蜡吧、備忘錄模式的動(dòng)機(jī)
? "曾經(jīng)有一段真摯的愛情放在我面前毫蚓,我沒有珍惜,等到失去了才后悔莫及昔善,如果上天再給我一次機(jī)會(huì)元潘,我會(huì)對(duì)她說三個(gè)字,我愛你耀鸦,如果非得加一個(gè)期限柬批,我希望是一萬(wàn)年。"有沒有很熟悉這句臺(tái)詞袖订,這句話出自周星馳主演的電影《大話西游之月光寶盒》。人生沒有“后悔藥”嗅虏,但是我們可以實(shí)現(xiàn)軟件中的“后悔藥”洛姑,這就需要了解備忘錄模式了。
2皮服、備忘錄模式的定義
? 備忘錄模式(Memento Pattern):在不破壞封裝的前提下楞艾,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)龄广,這樣可以在以后將對(duì)象恢復(fù)到原先保存的狀態(tài)硫眯。它是一種對(duì)象行為型模式,其別名為Token择同。
3两入、備忘錄模式的3個(gè)角色
1)Originator(原發(fā)器):它是一個(gè)普通類,可以創(chuàng)建一個(gè)備忘錄敲才,并存儲(chǔ)它的當(dāng)前內(nèi)部狀態(tài)裹纳,也可以使用備忘錄來恢復(fù)其內(nèi)部狀態(tài),一般將需要保存內(nèi)部狀態(tài)的類設(shè)計(jì)為原發(fā)器紧武。
2)Memento(備忘錄):存儲(chǔ)原發(fā)器的內(nèi)部狀態(tài)剃氧,根據(jù)原發(fā)器來決定保存哪些內(nèi)部狀態(tài)。備忘錄的設(shè)計(jì)一般可以參考原發(fā)器的設(shè)計(jì)阻星,根據(jù)實(shí)際需要確定備忘錄類中的屬性朋鞍。需要注意的是,除了原發(fā)器本身與負(fù)責(zé)人類之外妥箕,備忘錄對(duì)象不能直接供其他類使用滥酥,原發(fā)器的設(shè)計(jì)在不同的編程語(yǔ)言中實(shí)現(xiàn)機(jī)制會(huì)有所不同。
3)Caretaker(負(fù)責(zé)人):負(fù)責(zé)人又稱為管理者矾踱,它負(fù)責(zé)保存?zhèn)渫浐薇罚遣荒軐?duì)備忘錄的內(nèi)容進(jìn)行操作或檢查。在負(fù)責(zé)人類中可以存儲(chǔ)一個(gè)或多個(gè)備忘錄對(duì)象呛讲,它只負(fù)責(zé)存儲(chǔ)對(duì)象禾怠,而不能修改對(duì)象返奉,也無(wú)須知道對(duì)象的實(shí)現(xiàn)細(xì)節(jié)。
4吗氏、結(jié)構(gòu)圖
二芽偏、示例
? 備忘錄模式的核心是備忘錄類以及用于管理備忘錄的負(fù)責(zé)人類的設(shè)計(jì)。
1)先創(chuàng)建一個(gè)象棋棋子類Chessman弦讽,類中有一些屬性污尉、保存狀態(tài)和恢復(fù)狀態(tài)的方法,表示原發(fā)器往产;
2)然后創(chuàng)建棋子備忘錄ChessmanMemento類被碗,表示備忘錄;
3)最后創(chuàng)建MementoCaretaker類仿村,類中有添加備忘錄和獲取指定的備忘錄的方法锐朴,表示負(fù)責(zé)人;
具體代碼如下:
Chessman類:
// 象棋棋子類:原發(fā)器
@interface Chessman : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger x;
@property(nonatomic, assign) NSInteger y;
- (instancetype)initWithName:(NSString *)name x:(NSInteger)x y:(NSInteger)y;
- (ChessmanMemento *)save; //保存狀態(tài)
- (void)restore:(ChessmanMemento *)memento; //恢復(fù)狀態(tài)
@end
@implementation Chessman
- (instancetype)initWithName:(NSString *)name x:(NSInteger)x y:(NSInteger)y {
self = [super init];
if (self) {
_name = name;
_x = x;
_y = y;
}
return self;
}
- (ChessmanMemento *)save {
return [[ChessmanMemento alloc] initWithName:self.name x:self.x y:self.y];
}
- (void)restore:(ChessmanMemento *)memento {
self.name = memento.name;
self.x = memento.x;
self.y = memento.y;
}
@end
ChessmanMemento類:
// 象棋棋子備忘錄類
@interface ChessmanMemento : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger x;
@property(nonatomic, assign) NSInteger y;
- (instancetype)initWithName:(NSString *)name x:(NSInteger)x y:(NSInteger)y;
@end
@implementation ChessmanMemento
- (instancetype)initWithName:(NSString *)name x:(NSInteger)x y:(NSInteger)y {
self = [super init];
if (self) {
_name = name;
_x = x;
_y = y;
}
return self;
}
@end
MementoCaretaker類:
// 象棋棋子備忘錄管理類
@interface MementoCaretaker : NSObject
- (void)addMemento:(ChessmanMemento *)memento;
- (ChessmanMemento *)getMemento:(NSInteger)index;
@end
@interface MementoCaretaker ()
@property(nonatomic, strong) NSMutableArray *mementoList;
@end
@implementation MementoCaretaker
- (instancetype)init
{
self = [super init];
if (self) {
_mementoList = [NSMutableArray array];
}
return self;
}
- (void)addMemento:(ChessmanMemento *)memento {
[self.mementoList addObject:memento];
}
- (ChessmanMemento *)getMemento:(NSInteger)index {
if (index < self.mementoList.count) {
return self.mementoList[index];
}
return nil;
}
@end
運(yùn)行代碼:
@interface ViewController ()
@property(nonatomic, assign) NSInteger index; //備忘錄索引
@property(nonatomic, strong) MementoCaretaker *caretaker; //負(fù)責(zé)人
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.index = -1;
self.caretaker = [MementoCaretaker new];
Chessman *chess = [[Chessman alloc] initWithName:@"車" x:1 y:1];
[self playWithChess:chess];
chess.y = 8;
[self playWithChess:chess];
chess.x = 6;
[self playWithChess:chess];
NSLog(@"------------------");
[self undoWithChess:chess];
[self undoWithChess:chess];
}
// 下棋
- (void)playWithChess:(Chessman *)chess {
ChessmanMemento *memento = [chess save];
[self.caretaker addMemento:memento]; //保存?zhèn)渫? self.index ++; //序號(hào)加1
NSLog(@"下棋啦蔼囊,%@當(dāng)前位置:x = %ld, y = %ld", chess.name, chess.x, chess.y);
}
// 悔棋
- (void)undoWithChess:(Chessman *)chess {
self.index --; //序號(hào)減1
ChessmanMemento *memento = [self.caretaker getMemento:self.index];
[chess restore:memento]; //恢復(fù)備忘錄
NSLog(@"悔棋啦焚志,%@當(dāng)前位置:x = %ld, y = %ld", chess.name, chess.x, chess.y);
}
@end
打印結(jié)果:
下棋啦,車當(dāng)前位置:x = 1, y = 1
下棋啦畏鼓,車當(dāng)前位置:x = 1, y = 8
下棋啦酱酬,車當(dāng)前位置:x = 6, y = 8
------------------
悔棋啦,車當(dāng)前位置:x = 1, y = 8
悔棋啦云矫,車當(dāng)前位置:x = 1, y = 1
三膳沽、總結(jié)
? 備忘錄模式在很多軟件的使用過程中普遍存在,如果需要為軟件提供撤銷功能泼差,備忘錄模式無(wú)疑是一種很好的解決方案贵少。
1、優(yōu)點(diǎn)
1堆缘、它提供了一種狀態(tài)恢復(fù)的實(shí)現(xiàn)機(jī)制滔灶,使得用戶可以方便地回到一個(gè)特定的歷史步驟,當(dāng)新的狀態(tài)無(wú)效或者存在問題時(shí)吼肥,可以使用暫時(shí)存儲(chǔ)起來的備忘錄將狀態(tài)復(fù)原录平。
2、備忘錄實(shí)現(xiàn)了對(duì)信息的封裝缀皱,一個(gè)備忘錄對(duì)象是一種原發(fā)器對(duì)象狀態(tài)的表示斗这,不會(huì)被其他代碼所改動(dòng)。備忘錄保存了原發(fā)器的狀態(tài)啤斗,采用列表表箭、堆棧等集合來存儲(chǔ)備忘錄對(duì)象可以實(shí)現(xiàn)多次撤銷操作。
2钮莲、缺點(diǎn)
? 如果需要保存的原發(fā)器類的成員變量太多免钻,就不可避免需要占用大量的存儲(chǔ)空間彼水,每保存一次對(duì)象的狀態(tài)都需要消耗一定的系統(tǒng)資源。
3极舔、適用場(chǎng)景
1凤覆、保存一個(gè)對(duì)象在某一個(gè)時(shí)刻的全部狀態(tài)或部分狀態(tài),這樣以后需要時(shí)它能夠恢復(fù)到先前的狀態(tài)拆魏,實(shí)現(xiàn)撤銷操作盯桦。
2、防止外界對(duì)象破壞一個(gè)對(duì)象歷史狀態(tài)的封裝性渤刃,避免將對(duì)象歷史狀態(tài)的實(shí)現(xiàn)細(xì)節(jié)暴露給外界對(duì)象拥峦。
4、iOS應(yīng)用舉例
? Cocoa Touch框架在歸檔解檔卖子、屬性列表序列化和CoreData中采用了備忘錄模式事镣。
Demo地址:iOS-Design-Patterns