概要
はじめに
携帯電話で将棋を指していると、よく "repent"(悔い改める)という機能があります。 実は、"repent "とは、ある履歴状態に戻すことで、多くのソフトウェアでは "undo"(元に戻す)と呼ばれています。"undo "を実装するためには、まず履歴状態を保存しておく必要があります。"undo "を実行すると、ある履歴状態が取り出され、現在の状態に上書きされます。メモモードは、状態復元の実装メカニズムを提供し、ユーザーが特定の履歴ステップに簡単に戻ることができ、新しい状態が無効または問題がある場合、一時的に保存されたメモを使用して状態を復元することができます。
定義
メモ・モード: カプセル化を破壊することなくオブジェクトの内部状態をキャプチャし、この状態をオブジェクトの外部に保存します。
メモ・パターンは、トークン(Token)の別名を持つオブジェクト行動パターンです。
構造
役割
- オリジネーター:メモを作成し、現在の内部状態を保存する一般的なクラス、またはメモを使用して内部状態を復元できます。
- メメント:オリジネーターの内部状態を保存し、オリジネーターに基づいて保存する内部状態を決定します。メメントは、発信者とその責任者以外の他のオブジェクトが直接使用することはできません。
- 管理者:管理者としても知られ、メモを保存する責任があり、1つ以上のメモを保存することができ、メモオブジェクトを保存する責任があるだけで、メモを変更することはできません。
典型的な実装
ステップ
- オリジネーターの定義:オリジネーターは内部状態を保持する必要があるクラスで、現在の状態からメモを作成するメソッドと、メモから内部状態を復元するメソッドを提供します。
- メモの定義:発案者の内部状態を保存、カプセル化を考慮する必要あり、発案者と担当者以外のクラスからはアクセス不可、さもなければメモの意味を失う。
- 担当者の定義:メモオブジェクトを保持し、一般的にはコレクションを使用して複数のメモを保存します。
オリジネーター
class Originator
{
 private String state;
 public String getState() 
 {
 return this.state;
 }
 public void setState(String state) 
 {
 this.state = state;
 }
 public Memento save()
 {
 return new Memento(state);
 }
 public void restore(Memento memento)
 {
 this.state = memento.getState();
 }
}
originator の対応するフィールドは内部状態を表し、save() は内部状態をメモとしてカプセル化したオブジェクトを返し、restore() はメモ内の内部状態を取得して復元します。
メモ
class Memento
{
 private String state;
 public Memento(String state)
 {
 this.state = state;
 }
 public String getState() 
 {
 return this.state;
 }
}
単に内部状態を保持するクラスは、カプセル化を保証し、発案者と責任者以外のクラスからのアクセスを許可しない必要があります。
例えば、C++ではfriend friend、Javaではメモとオリジネーターを同じパッケージに入れないようにしたり、メモをオリジネーターの内部クラスとして使用するなど、言語によって実装の仕組みが異なります。
ヘッド
class Caretaker
{
 private List<Memento> mementos = new LinkedList<>();
 public Memento get()
 {
 return mementos.remove(0);
 }
 public void add(Memento memento)
 {
 mementos.add(0,memento);
 }
}
担当者はLinkedListを使って複数のメモを保存しています。 復旧操作は段階的、つまり一度に「2回取り消し」の履歴までは復旧できず、「1回取り消し」の履歴までしか復旧できないのでメモを保存するスタックを考えます。
クライアント
public static void main(String[] args)
{
 Originator originator = new Originator();
 Caretaker caretaker = new Caretaker();
 originator.setState(" ");
 caretaker.add(originator.save());
 originator.setState(" ");
 caretaker.add(originator.save());
 originator.setState(" ");
 caretaker.add(originator.save());
 originator.restore(caretaker.get());
 System.out.println(originator.getState());
 originator.restore(caretaker.get());
 System.out.println(originator.getState());
 originator.restore(caretaker.get());
 System.out.println(originator.getState());
}
クライアント側では、生成されたメモは、発信者の状態が変更されるたびに、責任者を経由して保存され、必要なときに責任者からメモが取り出され、適切な状態に復元されます。
出力:
例
Chess Repentanceの実装で、メモパターンを使って設計されています。
デザインは以下の通り:
- 元の送信者: Chessman
- メモ:メメント
- 担当:管理人
発案者は以下の通り:
class Chessman
{
 private String label;
 private int x;
 private int y;
 
 public Chessman(int x,int y,String label)
 {
 this.x = x;
 this.y = y;
 this.label = label;
 }
 public Memento save()
 {
 return new Memento(x,y,label);
 }
 public void restore(Memento memento)
 {
 this.x = memento.getX();
 this.y = memento.getY();
 this.label = memento.getLabel();
 }
	
	//setter+getter...
 @Override
 public String toString()
 {
 return "x:"+x+"\ty:"+y+"\tlabel:"+label;
 }
}
save()は内部状態をメモとして保存し、restore()は引数のメモを元に復元された内部状態を復元します。
覚書は以下の通り:
class Memento
{
 private int x;
 private int y;
 private String label;
 public Memento(int x,int y,String label)
 {
 this.x = x;
 this.y = y;
 this.label = label;
 }
 //getter...
}
プロパティはオリジネーター、そして最終的には担当者と一致します:
class Caretaker
{
 private List<Memento> mementos = new LinkedList<>();
 public Memento get()
 {
 return mementos.remove(0);
 }
 public void add(Memento memento)
 {
 mementos.add(0,memento);
 }
}
LinkedListを使ってスタック操作をシミュレートし、getでスタックの先頭の状態を取得します。
テスト:
public static void main(String[] args)
{
 Chessman chessman = new Chessman(1,2," ");
 Caretaker caretaker = new Caretaker();
 caretaker.add(chessman.save());
 chessman.setX(8);
 caretaker.add(chessman.save());
 chessman.setY(5);
 caretaker.add(chessman.save());
 chessman.restore(caretaker.get());
 System.out.println(chessman);
 chessman.restore(caretaker.get());
 System.out.println(chessman);
 chessman.restore(caretaker.get());
 System.out.println(chessman);
}
出力:
メモパッケージ
メモは特別なオブジェクトで、発信者だけが管理でき、担当者はメモを管理する責任があるだけで、他のクラスはメモに直接アクセスできないので、メモをカプセル化する必要があります。Javaでは内部クラスを使ってメモをカプセル化することができます。例えば、上記の例では次のように内部クラスをカプセル化することができます:
class Chessman
{
 //...
 public class Memento
 {
 private int x;
 private int y;
 private String label;
 
 public Memento(int x,int y,String label)
 {
 this.x = x;
 this.y = y;
 this.label = label;
 }
 
 public int getX() 
 {
 return this.x;
 }
 
 public int getY() 
 {
 return this.y;
 }
 
 public String getLabel() 
 {
 return this.label;
 }
 }
 //...
}
これにより、外部クラスによるメモへのアクセスが可能な限り制限されます。 さらに完全に制限するには、メモをプライベートな内部クラスとして設定し、そのメモを担当するクラスも作成者の内部クラスにして、外部クラスがメモにまったくアクセスできないようにします:
class Chessman
{
 //...
 private class Memento
 {
 //...
 }
 public class Caretaker
 {
 private List<Memento> mementos = new LinkedList<>();
 public Memento get()
 {
 return mementos.remove(0);
 }
 public void add(Memento memento)
 {
 mementos.add(0,memento);
 }
 }
 public Caretaker getCaretaker()
 {
 return new Caretaker();
 }
}
主な利点
- 状態復元:メモ・パターンは状態復元の実装を提供し、ユーザーが簡単に特定の履歴状態に戻ることを可能にします。
- 複数の取り消し:メモは、情報をカプセル化し、元の状態を保持し、リスト、スタック、その他のコレクションと連携して、複数の取り消し操作を実現します。
主な欠点
- リソースの消費量が大きい:多くのオリジネーターの状態を保存する必要がある場合、多くの記憶領域を占有します。オブジェクトの状態が保存されるたびに、一定のシステムリソースを消費する必要があります。
シナリオ
- オブジェクトの状態の全部または一部を保存し、後で必要なときに以前の状態に戻すことができます。
- 外部オブジェクトがオブジェクトの履歴状態のカプセル化を破壊するのを防ぎ、履歴状態の実装の詳細が外部オブジェクトに公開されるのを防ぎます。
概要





