導入
Commandは「操作をオブジェクトとして表現」——実行・取り消し・キュー化ができるように。Chain of Responsibilityは「処理役を数珠つなぎにし、扱える者が処理」——承認フローやミドルウェアの構造です。
いつもは calc.Add(50, 30); のようにその場で呼んで、その場で実行します。Commandはこれを「足し算をしてほしい」という注文票に変え、注文票をあとで・まとめて・順番に実行できるようにします。レストランの伝票を思い浮かべてください。お客さん(クライアント)が伝票を書き、ホール係(インヴォーカー)が伝票を溜めて厨房に流し、料理を作るのは調理人(レシーバー)です。
図解
Commandには5人の登場人物がいます。役割ごとにファイルを分けると、それぞれが何をする係なのかがはっきりします。
| 役割 | このサンプルでのファイル / クラス | 仕事 |
|---|---|---|
| インターフェース(Command) | ICalcCommand.cs | 「Execute() を持つ」という共通の形だけを決める |
| レシーバー(Receiver) | Receiver.cs | 実際の処理そのもの。足し算・引き算をする本体 |
| 実装クラス(ConcreteCommand) | AddCalcCommand.cs / SubtractCalcCommand.cs | 注文票。何を・どの値でやるかを持ち、レシーバーに委譲する |
| インヴォーカー(Invoker) | Invoker.cs | 注文票を溜めて(キュー)、順番に Execute() する |
| クライアント(Client) | Program.cs | 上の4つを組み立てて動かす側 |
classDiagram
class ICalcCommand {
<<interface>>
+Execute()
}
class AddCalcCommand {
-Receiver _receiver
+Execute()
}
class SubtractCalcCommand {
-Receiver _receiver
+Execute()
}
class Receiver {
+AddAction(first, second)
+SubtractAction(first, second)
}
class Invoker {
-Queue~ICalcCommand~ _calcCommands
+AddCommand(command)
+ExecuteAll()
}
class Program {
+Main()
}
ICalcCommand <|.. AddCalcCommand
ICalcCommand <|.. SubtractCalcCommand
AddCalcCommand --> Receiver
SubtractCalcCommand --> Receiver
Invoker o-- ICalcCommand
Program --> Invoker
Program --> Receiver
大事なのは、インヴォーカーは ICalcCommand しか知らないことです。足し算なのか引き算なのか、値がいくつなのかを一切知らないまま「溜めて、順に実行する」だけができます。だから後から掛け算コマンドを足しても、インヴォーカーは1行も変わりません。
サンプル:Command(役割ごとにファイルを分ける)
実務では役割ごとに別ファイルに置きます。下のエディタも同じ6ファイル構成になっていて、上のタブで切り替えられます。全ファイルはまとめてコンパイルされるので、そのまま「▶ 実行」を押せば動きます。
1. インターフェース(ICalcCommand.cs)。すべてのコマンドの土台。「Execute() を持っている」以外は何も決めません。
public interface ICalcCommand
{
void Execute();
}
2. レシーバー(Receiver.cs)。実際の処理内容はここだけにあります。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}");
}
}
3. 実装クラス(AddCalcCommand.cs)。注文票そのもの。「誰に(レシーバー)」「どの値で」やってもらうかをコンストラクタで受け取って持っておき、Execute() で呼び出すだけです。自分では計算しません。
// 「和を出して」という注文票
public class AddCalcCommand : ICalcCommand
{
private readonly Receiver _receiver;
private readonly double _first;
private readonly double _second;
public AddCalcCommand(Receiver receiver, double first, double second)
{
_receiver = receiver;
_first = first;
_second = second;
}
public void Execute()
{
_receiver.AddAction(_first, _second); // 処理はレシーバーに任せる
}
}
4. もう1つの実装クラス(SubtractCalcCommand.cs)。形はまったく同じで、呼ぶメソッドだけが違います。「操作の種類=クラスの種類=ファイルの数」になるのがCommandの特徴です。
// 「差を出して」という注文票
public class SubtractCalcCommand : ICalcCommand
{
private readonly Receiver _receiver;
private readonly double _first;
private readonly double _second;
public SubtractCalcCommand(Receiver receiver, double first, double second)
{
_receiver = receiver;
_first = first;
_second = second;
}
public void Execute()
{
_receiver.SubtractAction(_first, _second);
}
}
5. インヴォーカー(Invoker.cs)。注文票を Queue に溜めて、先に入れたものから実行します。中身を知らないので、溜める・並べる・あとで流すが自由にできます。
// 注文票を溜めて順に流す人(ホール係)
public class Invoker
{
private readonly Queue<ICalcCommand> _calcCommands = new Queue<ICalcCommand>();
public void AddCommand(ICalcCommand command)
{
_calcCommands.Enqueue(command); // 溜めるだけ。まだ実行しない
}
public void ExecuteAll()
{
while (_calcCommands.Count > 0)
{
var current = _calcCommands.Dequeue();
current.Execute(); // 入れた順に実行
}
}
}
AddCommand した時点ではまだ何も起きず、ExecuteAll() で初めて動く——この「頼む」と「実行する」のズレこそがCommandの効き目です。ズレがあるから、履歴に残す・あとで取り消す・順番を入れ替える・別スレッドで流す、といったことが後付けできます。
サンプル:Chain of Responsibility
こちらは「扱えなければ次の人へ回す」パターン。承認額に応じて担当者が変わる稟議フローがそのまま形になります。担当者1人=クラス1つ=ファイル1つです。
flowchart LR
R["要求"] --> H1["担当A<br/>扱える?"]
H1 -->|無理| H2["担当B<br/>扱える?"]
H2 -->|無理| H3["担当C<br/>処理"]
6. 承認者の共通の形(Approver.cs)。「次の人」を持てるようにするのが連鎖の肝です。
public abstract class Approver
{
protected Approver? Next;
protected Approver(Approver? next) => Next = next;
public abstract void Approve(int amount);
}
7. 各担当者(Leader.cs / Manager.cs)。承認段階を増やすときはファイルを1つ足して、つなぎ方を変えるだけです。
public class Leader : Approver
{
public Leader(Approver? next) : base(next) { }
public override void Approve(int amount)
{
if (amount <= 100000) Console.WriteLine($"課長が承認: {amount}円");
else Next?.Approve(amount); // 扱えなければ次へ回す
}
}
public class Manager : Approver
{
public Manager(Approver? next) : base(next) { }
public override void Approve(int amount) => Console.WriteLine($"部長が承認: {amount}円");
}
8. クライアント(Program.cs)。役者をそろえて注文票を積み、最後に一気に実行します。承認の連鎖もここで組み立てます。
// ---- Command ----
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
// ---- Chain of Responsibility ----
var chain = new Leader(new Manager(null));
chain.Approve(80000); // 課長が承認: 80000円
chain.Approve(300000); // 部長が承認: 300000円
- Command: 操作を
Execute()を持つオブジェクトにし、履歴・キュー・取り消しに対応できる - Chain of Responsibility: 処理役を連結し、「自分が扱えなければ次へ回す」
- どちらも「呼び出し側」と「処理内容」を切り離す
やってみよう
下のエディタはこのサンプルの全ファイルが入った状態です。タブを切り替えながら、次の順で書き換えてみましょう。役割分担のありがたみが体感できます。
Program.csのAddCommandの2行を入れ替える → 実行順だけが変わり、他のファイルは無傷。- 掛け算コマンドを足す(
Receiver.csにMultiplyActionを追加し、AddCalcCommand.csをまねた実装クラスを書く)→Invoker.csは1行も変えずに掛け算が流せる。 ExecuteAll()の呼び出しを消す → 何も計算されないことを確認。「頼む」と「実行する」が別物だとわかります。Leader.csの上限額(100000)を変える → 承認が課長で止まるか部長へ回るかが変わり、Program.csは無変更。
演習
ICommand cmd = new GreetCommand("Taro");
cmd.Execute(); // こんにちは、Taro
// TODO: Execute() で "こんにちは、名前" を出力する GreetCommand を定義してください
// (実装クラス=注文票。名前をコンストラクタで受け取り、フィールドに持っておきます)
interface ICommand { void Execute(); }
___
- 期待される出力:
こんにちは、Taro
ヒント1を見る
名前をフィールドに持ち、Executeで出力します
ヒント2を見る
class GreetCommand : ICommand { private readonly string _name; public GreetCommand(string name) => _name = name; public void Execute() => Console.WriteLine($"こんにちは、{_name}"); }
まとめ
- Commandは操作をオブジェクト化し、実行・履歴・取り消しを可能にする
- 登場人物は インターフェース/レシーバー/実装クラス/インヴォーカー/クライアント の5つ。ファイルを分けると役割がはっきりする
- インヴォーカーはインターフェースしか知らないので、コマンドを増やしても壊れない
- Chain of Responsibilityは処理役を連結し順に委ねる
- どちらも呼び出しと処理内容を分離する
次回: 手順の骨組みと反復のTemplate Method/Iteratorです。