今回はMementoパターンを取り上げよう。「memento」とは遺品、記念物といった意味になる。オブジェクトの状態を記念としてとっておくためのパターンだ。
Mementoパターンとは
Mementoは、オブジェクトの状態を保存しておき、後でそれを元に戻すことができるようにするパターンだ。何をやるためのものかと言えば、真っ先に挙がるのはアンドゥだろう。ドキュメントの編集を行うタイプのアプリケーションでは、絶対に外すことのできないアンドゥ機能。これを実現するためのものだと考えればよい。
アンドゥを実現する方法はいくつか考えられるが、mementoで行うのは、オブジェクトのスナップショットを取るというものだ。現在のオブジェクトの状態を取得し、それをmementoオブジェクトとして保存しておく。アンドゥを行うときは、保存しておいたmementoオブジェクトから状態を取り出して、オブジェクトに適用してやればいい。
Mementoが提唱する利点は、アプリケーションは対象となるオブジェクトの内部構造を知らずに済むことだ。複雑な内部状態はMementoがカプセル化するので、Mementoの保存にだけ気を配っていればいい。反面、多くのメモリを使用してコストが高く付ことが予想されるだろう。これには、変化分のみを保存するようにすればいいのではないか、と提案されている。
Mementoパターンの登場人物
Mementoパターンの登場人物を紹介しよう。まず、アンドゥの対象となるクラスだ。これは、Originatorと呼ばれる。Originatorはある状態を持ち、アプリケーションでの作業にともなって変化する。これの状態を、アンドゥで元に戻せるようにする訳だ。
次の登場人物は、Mementoクラスだ。Originatorの状態を保持することになる。つまり、Originatorのスナップショットとは何かと問われれば、Mementoそのものである、と言える。Originatorは、Mementoを作成するメソッド、そしてMementoを適用するメソッドを持つことになる。
最後に、Mementoの作成と適用を行う、Caretakerクラスがある。このクラスは、適切なタイミングでMementoを作成し、その履歴を保存する。アプリケーションからのアンドゥの呼び出しに応じて、適切にMementoを適応していくのだ。
これら3者の関係は、次の図のようになるだろう。
Mementoパターンの実装
ではいつものように、MementoパターンをObjective-Cで実装してみよう。
このサンプルでは、保存する状態として、int型のstateという名前の変数1個を使うものとする。もちろん、実際のアプリケーションでは、こんな簡単なものは存在しないであろうが。
まずMementoクラスをの定義と実装を紹介しよう。次のようにする。
List 1.
@interface Memento : NSObject
{
int _state;
}
- (int)state;
- (void)setState:(int)state;
@end
@implementation Memento
- (int)state
{
return _state;
}
- (void)setState:(int)state
{
_state = state;
}
@end
状態として、インスタンス変数_stateを定義し、そのアクセッサメソッドを提供する。これだけである。スナップショットとしては、これで充分であろう。
次に、Originatorクラスを考えよう。このクラスが状態を持ち、それがアンドゥの対象になるのである。次のように宣言してみた。
List 2.
@interface Originator : NSObject
{
int _state;
}
- (Memento*)createMemento;
- (void)setMemento:(Memento*)memento;
@end
インスタンス変数_stateを定義した。これが、現在作動中の状態である。先ほどのMementoの_stateと混同しないでほしい。あちらは、このクラスのスナップショットたる値、ということになる。
そして、メソッドを2つ宣言した。1つは、現在のスナップショットを作成するcreateMemento。もう1つは、過去に作成したMementoの状態に戻すsetMemento:メソッドだ。これらの実装は、次のようになる。
List 3.
@implementation Originator
- (Memento*)createMemento
{
Memento* memento;
memento = [[[Memento alloc] init] autorelease];
[memento setState:_state];
return memento;
}
- (void)setMemento:(Memento*)memento
{
_state = [memento state];
}
@end
そして最後は、Caretakerクラスだ。このクラスはOriginatorを操作して、適時スナップショットを取ることになる。従って、OriginatorとMementoへの参照が必要だ。
List 4.
@interface Caretaker : NSObject
{
Originator* _originator;
Memento* _memento;
}
- (void)setOriginator:(Originator*)originator;
- (void)save;
- (void)restore;
@end
インスタンス変数に、OriginatorとMementoがある。ここでは簡単にするために、Mementoは1つだけ保存することにした。そして、スナップショットを取るsaveメソッド、アンドゥを行うresotreメソッドを用意した。
ここまで入念に用意を行ったので、実装は大体想像がつくと思う。
List 5.
@implementation Caretaker
- (void)setOriginator:(Originator*)originator
{
_originator = originator;
}
- (void)save
{
_memento = [_originator createMemento];
}
- (void)restore
{
[_originator setMemento:_memento];
}
@end
これで、Mementoの3つのクラスを実装できた。使い方は次のようになるだろう。
List 6.
Originator* originator;
Caretaker* caretaker;
originator = [[Originator alloc] init];
caretaker = [[Caretaker alloc] init];
[caretaker setOriginator:originator];
...
[caretaker save];
...
[caretaker restore];
次回は、Mementoパターンが目的にしているもの、ということで、Cocoaでのアンドゥ機能を紹介したいと思う。先に述べておくが、Mementoパターンでは、ない。