はじめに
本記事は、筆者が作成したデザインパターンのサンプルの中で、Webで記載されているものより分かりやすいと判断したサンプルを共有することを目的としています。
Mementoパターンとは
オブジェクト状態の保存と復元を実現するときに活用できるデザインパターンです。
最大の特徴はオブジェクトをMementoで保存するということです。Mementoはオブジェクトの状態を表す情報のみを格納する役割を持っています。状態を表すデータしか保持しないため、オブジェクトそのものよりデータ量が少ないです。そのため、Mementoを使ってオブジェクトの保持・復元を実現する場合には、オブジェクトそのものを保持・復元する場合に比べて使用するメモリ量を減らせるメリットがあります。
Mementoパターンのサンプル
Mementoパターンの一般的なサンプルは以下の通り、Web上に数多くあります。
- http://www.itsenka.com/contents/development/designpattern/memento.html
- http://www.ie.u-ryukyu.ac.jp/~e085739/java.it.19.html
- https://www.techscore.com/tech/DesignPattern/Memento.html/
しかし、上記のサンプルはあまり一般的ではなく、実用性のないサンプルであると私は感じました。
そこで、スナップショット機能をテーマとした実用性のあるサンプルを作成しました。
次項でそのサンプルを示します。
自己流のMementoパターンのサンプル
サンプルのテーマはスナップショット機能の付いた超簡易版テキストエディタです。
以下にクラス図とソースコード(C#で実装)を示します。
クラス図
それぞれのクラスの責務を説明していきます。
-
TextEditorクラス
テキストの追記とスナップショットの作成、スナップショットからの状態復元を責務として持ちます。
AppendTextメソッドでテキストの追記、PrintTextメソッドでその時点でのテキストを表示します。
CreateMementoメソッドは、メソッドが呼び出された時点でのMementoを作成します。
SetMementoメソッドは、指定したMementoを使って状態を復元します。
一般的なMementoパターンでのOriginator(作成者)と同じ責務を持ちます。
OriginatorはMementoの作成とMementoからの状態の復元を行います。 -
TextEditorMementoクラス
TextEditorの内部情報を保持する責務を持ちます。
TextEditorクラスの状態を復元するためにテキスト情報を保持します。
一般的なMementoパターンでのMemento(形見)と同じ責務を持ちます。
MementoはOriginatorの内部情報を保持します。 -
TextEditorSnapShotManagerクラス
TextEditorのスナップショットの保存、指定したスナップショットにTextEditorを復元する責務を持ちます。
SaveSnapShotメソッドで名前を付けてスナップショットを保存します。
LoadSnapShotメソッドで指摘した名前のスナップショットにTextEditorを復元します。
対象とするTextEditorはコンストラクタで受け取ります。
一般的なMementoパターンでのCaretaker(世話人)と同じ責務を持ちます。
CaretakerはOriginatorの状態の保存と復元を責務として持ちます。 -
Clientクラス
TextEditorを利用する責務を持ちます。
TextEditorとTextEditorSnapShotManagerを使って、TextEditorのスナップショットの作成、スナップショットからの復元の動作を確認します。
ソースコード
- TextEditorクラス
/// <summary>
/// テキストエディタ
/// メメントの作成とメメントからの状態の復元を行う
/// </summary>
public class TextEditor
{
/// <summary>
/// テキスト
/// </summary>
private string m_Text = string.Empty;
/// <summary>
/// テキストの追記
/// </summary>
/// <param name="text">追記するテキスト</param>
public void AppendText(string text)
{
m_Text += text;
}
/// <summary>
/// テキストの表示
/// </summary>
public void PrintText()
{
Console.WriteLine(m_Text);
}
/// <summary>
/// 現在のテキストエディタの状態を表すメメントを作成する
/// </summary>
/// <returns>作成したメメント</returns>
public TextEditorMemento CreateMemento()
{
return new TextEditorMemento(m_Text);
}
/// <summary>
/// メメントからテキストエディタの状態を復元する
/// </summary>
/// <param name="memento">テキストエディタのメメント</param>
public void SetMemento(TextEditorMemento memento)
{
m_Text = memento.Text;
}
- TextEditorMementoクラス
/// <summary>
/// テキストエディタ用のメメント
/// テキストエディタの状態を復元するために、テキスト情報を保持する
/// </summary>
public class TextEditorMemento
{
/// <summary>
/// テキスト
/// </summary>
public string Text { get; }
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="text"></param>
public TextEditorMemento(string text)
{
Text = text;
}
}
- TextEditorSnapShotManagerクラス
/// <summary>
/// テキストエディタのスナップショットを管理する。
/// テキストエディタの状態の保存と復元を行う。
/// </summary>
public class TextEditorSnapShotManager
{
/// <summary>
/// 管理するテキストエディタ
/// </summary>
private TextEditor m_Editor;
/// <summary>
/// スナップショットを管理するためのディクショナリ
/// 名前を付けてスナップショットを保持する
/// </summary>
private Dictionary<string, TextEditorMemento> m_MementoDictionary = new Dictionary<string, TextEditorMemento>();
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="editor">管理するテキストエディタ</param>
public TextEditorSnapShotManager(TextEditor editor)
{
m_Editor = editor;
}
/// <summary>
/// 名前を付けてスナップショットを保存する
/// </summary>
/// <param name="snapShotName">スナップショット名</param>
public void SaveSnapShot(string snapShotName)
{
m_MementoDictionary.Add(snapShotName, m_Editor.CreateMemento());
}
public bool LoadSnapShot(string snapShotName)
{
if (m_MementoDictionary.ContainsKey(snapShotName) == false)
{
return false;
}
m_Editor.SetMemento(m_MementoDictionary[snapShotName]);
return true;
}
}
- Clientクラス
/// <summary>
/// サンプルの動作を確認するクラス
/// </summary>
public class Client
{
/// <summary>
/// Mainメソッド
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
Do();
}
/// <summary>
/// 動作を確認する
/// </summary>
private static void Do()
{
// テキストエディタとスナップショットマネージャの作成
var editor = new TextEditor();
var snapShotManager = new TextEditorSnapShotManager(editor);
// テキストの追記
editor.AppendText("A");
// スナップショットの保存
snapShotManager.SaveSnapShot("FirstSnapShot");
// さらにテキストを追加
editor.AppendText("B");
// 表示
editor.PrintText();
// スナップショットから状態を復元
if (snapShotManager.LoadSnapShot("FirstSnapShot"))
{
// 復元後の状態でテキストを表示
editor.PrintText();
}
}
}
このサンプルのどこがいいのか
2点あると考えています。
まずは、スナップショット機能という比較的実用的なサンプルである点です。スナップショット機能を作る際にはある程度は参考になるのではないかと思います。
次にクラスごとの責務が明確な点です。通常のサンプルでは、Caretakerとして振る舞うクラスがCaretakerを使う責務も持っており、責務分割がされていません。一方、上記のサンプルでは、Caretakerとして振る舞うクラス(TextEditorSnapShotManagerクラス)とCaretakerを使うクラス(Clientクラス)がしっかり分割されています。