Commandパターンとは?
デザインパターンの一種で、命令や動作をオブジェクトで表現する設計技法のことです。
イメージとしては1クラスの中に1命令の実行に必要な手続きとデータが収まっている感じです。
wikiには以下のように記載されています。
Commandパターンでは何かリクエストを実行する際、単純に処理を実行するのではなく、次のステップを踏む。
1.処理をメソッドとして内包するCommandクラスの定義
2.Commandオブジェクトの生成
3.Command.execute()メソッドのコールによるリクエスト実行
すなわちリクエストを「手順書」の定義・生成とその「実行」に段階分けするパターンをとる。
クラス図
メリット
- 実行単位ごとにクラス分けしておくことによって各Commandが疎結合に
- Invokerによる実行順序の一元管理で実行履歴保存やundoが容易になる
- ClientとCommandの責務の分離が明確になる
C#の実装例
さっそく実装してみます!
今回は二つの数の和と差を求めるプログラムを作ります。
ICommand
各Commandの元となるインターフェース
public interface ICalcCommand
{
void Execute();
}
Receiver
Commandから呼び出される内部の処理を実装します。
public class Receiver
{
public void AddAction(double first, double second)
{
Console.WriteLine($"和: {first + second}");
}
public void SubtractAction(double first, double second)
{
Console.WriteLine($"差: {first - second}");
}
}
ConcreteCommand
インターフェースの実装クラスです。
注目ポイントは、各Commandの細かい処理の実装はReceiverに委譲しているということです。
// 足し算
public class AddCalcCommand : ICalcCommand
{
private readonly Receiver _receiver;
private readonly double _first;
private readonly double _second;
public AddCalcCommand(Receiver receiver, double first, double second)
{
this._receiver = receiver;
this._first = first;
this._second = second;
}
public void Execute()
{
_receiver.AddAction(_first, _second); // Recieverの処理を呼び出し
}
}
// 引き算
public class SubtractCalcCommand : ICalcCommand
{
private readonly Receiver _receiver;
private readonly double _first;
private readonly double _second;
public SubtractCalcCommand(Receiver receiver, double first, double second)
{
this._receiver = receiver;
this._first = first;
this._second = second;
}
public void Execute()
{
this._receiver.SubtractAction(_first, _second); // Recieverの処理を呼び出し
}
}
Invoker
Commandの格納と呼び出しを管理するInvokerです。
順序の管理と一斉呼び出しを担います。
public class Invoker
{
private readonly Queue<ICalcCommand> _calcCommands = new Queue<ICalcCommand>();
public void AddCommand(ICalcCommand command)
{
this._calcCommands.Enqueue(command);
}
public void ExecuteAll()
{
while (_calcCommands.Count > 0)
{
var current = _calcCommands.Dequeue();
current.Execute();
}
}
}
Client
後はClientから作り上げたパターンを呼び出すだけです。
public class Program
{
public static void Main()
{
var firstNum = 50;
var secondNum = 30;
var receiver = new Receiver();
var invoker = new Invoker();
invoker.AddCommand(new AddCalcCommand(receiver, firstNum, secondNum));
invoker.AddCommand(new SubtractCalcCommand(receiver, firstNum, secondNum));
Console.WriteLine($"{firstNum}と{secondNum}の和と差は?");
invoker.ExecuteAll();
}
}
実行結果
50と30の和と差は?
和: 80
差: 20
疑問に思ったこと
「Receiverって本当に必要なの?🤔」
Reciverに分けなくてもConcreteCommandに内部処理そのまま実装してしまえばよくないか?とCommandパターン初心者の私は思ったわけです。
wikiを見るとReceiverは必ずしもCommandパターンの要件ではないようです...
が、以下の点からReceiverを設けて処理を委譲するメリットは大きいとのことです。
- 処理の実体がConcreteCommandに混ざらず、設計が単純になる
- 似た処理を持つ複数のCommandのが増えなくなる
- テストやモックがしやすくなる
ぜひ今後の実装の一案に加えてみてはいかがでしょうか🙆♂️
参考