C#のコマンドパターンで操作履歴を管理!Undo機能の実装方法を完全解説
生徒
「C#でアプリを作っているのですが、間違えて操作したときに元に戻すUndo機能を作るのが難しくて困っています。」
先生
「なるほど。そんなときはコマンドパターンという設計手法を使うのが一番の近道ですよ。」
生徒
「コマンドパターンですか?初めて聞く言葉ですが、どういう仕組みなんですか?」
先生
「操作を一つ一つの部品として扱うことで、履歴を保存したり、取り消したりするのがとても簡単になるんですよ。」
1. デザインパターンとコマンドパターンの基礎知識
プログラミングの世界には、多くの先輩たちが積み上げてきた便利な設計の型があります。これをデザインパターンと呼びます。その中でもコマンドパターンは、命令そのものを一つのオブジェクト(データのかたまり)として扱う手法です。
通常、プログラムは「このボタンが押されたら、この関数を動かす」といった直接的な命令になりがちですが、コマンドパターンを使うと「ボタンが押された」という事実を「コマンド」という箱に入れて管理します。これにより、後からその箱を調べたり、逆の処理を実行したりすることが可能になります。初心者の方がプログラムを学ぶ上で、この「命令をデータ化する」という考え方は非常に強力な武器になります。
2. なぜコマンドパターンで操作履歴が管理できるのか
操作履歴を管理するとは、言い換えれば「過去に行った命令を順番に記録しておく」ということです。コマンドパターンを使わない場合、何が行われたかを追いかけるのはとても大変です。しかし、コマンドパターンで「実行ボタンを押した」「テキストを入力した」といった操作をすべて独立したコマンドとして作っておくと、それらをリストという入れ物に順番に入れておくだけで履歴管理が完了します。
これがなぜ便利かというと、履歴リストの中に保存されたコマンドを取り出して、反対の動作をさせるだけで、簡単に「一つ前に戻る」といった操作を実現できるからです。プログラミング未経験の方にとって、まずは「操作を箱に入れる」というイメージを持つことが、複雑なシステムを理解する第一歩になります。
3. インターフェースを使って操作を共通化する
コマンドを扱うために重要なのがインターフェースという仕組みです。インターフェースは「必ずこの機能を持たせます」という決まりごとを定義するものです。例えば「実行する」ためのメソッドと「取り消す」ためのメソッドを持つインターフェースを作ることで、どんな操作でも同じ手順で扱えるようになります。
public interface ICommand
{
void Execute();
void Undo();
}
このICommandというインターフェースがあれば、色々な操作を同じ枠組みで管理できます。インターフェースという用語は難しく感じるかもしれませんが、要は「誰が作っても同じ操作ができるようにするための約束事」だと考えてください。
4. 具体的なコマンドクラスを作成してみよう
次に、実際に文字を書き換えるという操作をコマンドとして作ってみます。このクラスには、本来の値と新しい値を保存しておくことで、取り消しが可能になります。
public class ChangeTextCommand : ICommand
{
private string _previousText;
private string _newText;
public ChangeTextCommand(string previous, string next)
{
_previousText = previous;
_newText = next;
}
public void Execute()
{
Console.WriteLine("新しい文字を設定します: " + _newText);
}
public void Undo()
{
Console.WriteLine("前の文字に戻します: " + _previousText);
}
}
このように、一つの操作に対して「やる(Execute)」と「戻す(Undo)」の両方をセットにしておきます。これにより、プログラムは「今何が起きたか」だけでなく「どうすれば元に戻せるか」という情報もセットで保持できるようになります。
5. 操作履歴リストを作成して管理する方法
コマンドの準備ができたら、それを蓄積するための履歴リストを作りましょう。C#のStack(スタック)というデータ構造を使うと、最新の履歴から順に取り出すことができます。スタックは「積み上げた皿」のようなもので、一番最後に置いたものから先に取り出す仕組みです。これがまさに「操作の取り消し」の動きにぴったりです。
using System.Collections.Generic;
public class CommandManager
{
private Stack<ICommand> _history = new Stack<ICommand>();
public void AddAndExecute(ICommand command)
{
command.Execute();
_history.Push(command);
}
public void UndoLastCommand()
{
if (_history.Count > 0)
{
ICommand lastCommand = _history.Pop();
lastCommand.Undo();
}
}
}
このCommandManagerを使えば、難しいことは一切考えずにコマンドを実行するだけで、裏側で勝手に履歴が管理されます。Pop(ポップ)という操作で履歴からコマンドを取り出し、即座にUndoを呼び出すことで元通りになります。
6. 実際に動かして動作を確認する
最後に、これまで作った仕組みを実際に動かしてみましょう。メインの処理では、コマンドを作成し、マネージャーに渡すだけで処理が完了します。
class Program
{
static void Main()
{
CommandManager manager = new CommandManager();
ICommand cmd1 = new ChangeTextCommand("初期値", "設定A");
manager.AddAndExecute(cmd1);
manager.UndoLastCommand();
}
}
このプログラムを実行すると、以下のように動作します。
新しい文字を設定します: 設定A
前の文字に戻します: 初期値
このように、コマンドパターンを使うことで、操作を部品化して管理することが可能になります。最初は難しく感じるかもしれませんが、一度「操作を箱に入れる」という感覚が身につけば、どんな複雑なアプリでもUndo機能を実装できるようになります。ぜひこの設計パターンをマスターして、より便利で使いやすいプログラム作りに挑戦してみてください。