ダイアログ編集にMementoパターン
先日のエントリーのコメントにてMementoパターン使用のご提案を頂きましたので、考えてみました。
まず、Mementoに気づかず実装した、現在のコード。
- 編集ダイアログのコンストラクタでモデルのコピー(workspaceModel)を作成し、それに対して編集を行う。
- 編集結果は、getWorkspaceModel() で取得する。
- OKだった場合、オリジナルモデルにworkspaceModelを反映させるコマンド(EditTableCommand)を発行する。
- Cancelだった場合、何もしない。
public class TableEditPart extends … implements … { public void doubleClicked() { // オリジナルモデルを取得 final TableModel tableModel = (TableModel) getModel(); final DatabaseModel dbModel = (DatabaseModel) getParent().getModel(); // ダイアログを生成 final TableEditDialog dialog = new TableEditDialog( getViewer().getControl().getShell(), tableModel, dbModel); // 実行 if(dialog.open() == Dialog.OK) { // OK だった場合 final GraphicalViewer viewer = (GraphicalViewer) getViewer(); // テーブル編集コマンドを発行 viewer.getEditDomain().getCommandStack().execute( new EditTableCommand(tableModel, dialog.getWorkspaceModel())); } } } public class TableEditDialog extends Dialog { /** 編集用のモデルコピー */ private TableModel workspaceModel; public TableEditDialog( final Shell parentShell, final TableModel tableModel, final DatabaseModel dbModel) { // 略 // モデルのコピーを取得する。 workspaceModel = (TableModel) tableModel.getWorkspaceModel(); } // 略 (各種編集はworkspaceModelに対して行う) /** 編集結果を取得する */ public TableModel getWorkspaceModel() { return workspaceModel; } } public class EditTableCommand extends Command { /** 編集対象のモデル */ private final TableModel targetTableModel; /** 編集前のコピーモデル */ private final TableModel oldTableWorkspaceModel; /** 編集後のコピーモデル */ private final TableModel newTableWorkspaceModel; public EditTableCommand( final TableModel targetTableModel, final TableModel newTableWorkspaceModel) { super(); this.targetTableModel = targetTableModel; // 編集前の状況をコピーとして保存 this.oldTableWorkspaceModel = (TableModel) targetTableModel.getWorkspaceModel(); this.newTableWorkspaceModel = newTableWorkspaceModel; } @Override public void execute() { // 編集後の状況をターゲットに反映 targetTableModel.setWorkspaceModel(newTableWorkspaceModel); } @Override public void undo() { // 編集前の状況をターゲットに反映 targetTableModel.setWorkspaceModel(oldTableWorkspaceModel); } }
では、これをMementoパターン版にしてみよう。
さて、これをMementoパターン版に変えてみました。
- 事前に編集前のスナップショットを保存する。
- 編集ダイアログでは、オリジナルモデルを直接編集する。
- 編集後のスナップショットを保存する。
- OKだった場合、EditTableCommandを発行する。(※)
- Cancelだった場合、編集前のスナップショットを利用して、オリジナルモデルを元に戻す。
public class TableEditPart extends … implements … { public void doubleClicked() { // オリジナルモデルを取得 final TableModel tableModel = (TableModel) getModel(); final DatabaseModel dbModel = (DatabaseModel) getParent().getModel(); // 編集前のスナップショットを保存 final TableModel beforeEditMemento = tableModel.createMemento(); // ダイアログを生成 final TableEditDialog dialog = new TableEditDialog( getViewer().getControl().getShell(), tableModel, dbModel); // 実行 if(dialog.open() == Dialog.OK) { // OK だった場合 // 編集後のスナップショットを保存 final TableModel afterEditMemento = tableModel.createMemento(); final GraphicalViewer viewer = (GraphicalViewer) getViewer(); // テーブル編集コマンドを発行 viewer.getEditDomain().getCommandStack().execute( new EditTableCommand(tableModel, beforeEditMemento, afterEditMemento)); } else { // Cancel だった場合 // 編集前にロールバック tableModel.setMemento(beforeEditMemento); } } } public class TableEditDialog extends Dialog { /** オリジナルのモデル */ private TableModel tableModel; public TableEditDialog( final Shell parentShell, final TableModel tableModel, final DatabaseModel dbModel) { // 略 this.tableModel = tableModel; } // 略 (各種編集はオリジナルのモデルに対して直接行う) } public class EditTableCommand extends Command { private final TableModel targetTableModel; private final TableModel beforeEditMemento; private final TableModel afterEditMemento; public EditTableCommand( final TableModel targetTableModel, final TableModel beforeEditMemento, final TableModel afterEditMemento) { super(); this.targetTableModel = targetTableModel; this.beforeEditMemento = beforeEditMemento; this.afterEditMemento = afterEditMemento; } @Override public void execute() { // 編集後のスナップショットをオリジナルに反映 targetTableModel.setMemento(afterEditMemento); } @Override public void undo() { // 編集前のスナップショットをオリジナルに反映 targetTableModel.setMemento(beforeEditMemento); } }
こんな感じでしょうか。
ただ、気になるのは上記手順(※)でマークした部分。
ダイアログ実行後、既にオリジナルモデルには変更が反映されています。(オリジナルを直接編集しているので、当たり前ですね。)従って、EditTableCommand を実行する必要は無い訳です。が、テーブルの編集をUNDO/REDOする為に、コマンド発行が必要になってしまいます。
というわけで、UNDO/REDOでは無い、初回編集時にも、execute() が実行され、あまり意味の無いことをしてしまいます。
このままでも問題はないのでしょうけど…。
では、どうすれば効率が良くなるだろうか。
Command クラスを覗いてみました。
public abstract class Command { public void execute() { } public void undo() { } public void redo() { execute(); } // 略 }
おー。redo() ってのがあるじゃないですか。コイツをオーバーライドしてしまえば良いんですね。
execute() はオーバーライドしなければ、何もしませんので、放置。
public class EditTableCommand extends Command { private final TableModel targetTableModel; private final TableModel beforeEditMemento; private final TableModel afterEditMemento; public EditTableCommand( final TableModel targetTableModel, final TableModel beforeEditMemento, final TableModel afterEditMemento) { super(); this.targetTableModel = targetTableModel; this.beforeEditMemento = beforeEditMemento; this.afterEditMemento = afterEditMemento; } @Override public void undo() { // 編集前のスナップショットをオリジナルに反映 targetTableModel.setMemento(beforeEditMemento); } @Override public void redo() { // 編集後のスナップショットをオリジナルに反映 targetTableModel.setMemento(afterEditMemento); } }
これで、スマートになったでしょうか。