メインコンテンツへ移動

デザインパターンにおけるコマンドパターン(Command Pattern)

無料2015-03-06#Design_Pattern#命令模式#Command Pattern

コマンドパターンは、リクエスト送信者と実行者の結合を解くためのデザインパターンです。その拡張応用には主にキューリクエスト(演算を指定されたワーカースレッドに制限)とログリクエスト(ログ生成およびトランザクション復元に使用)があります。

no_mkd

一.コマンドパターンとは?

コマンドパターンは、メソッド呼び出しの詳細をカプセル化し、リクエスト送信者と実行者の結合を解きます。具体的なフローは以下の通りです:

1.リクエスト送信者(クライアント)の視点から

リクエスト送信者(クライアント)がリクエストを送信 -> 呼び出し者(システム)がコマンドオブジェクトを構築してリクエストをカプセル化 -> 呼び出し者がコマンドオブジェクトの指定メソッドを呼び出す(リクエストが実行される)。明らかに、リクエスト送信者は実行者が誰なのか全く知らず、具体的な実行詳細も知りません。もちろんリクエスト送信者自体もそれらを気にしておらず、リクエストが実行されたことさえ知っていればよいのです。

2.実行者(低層コンポーネント)の視点から

実行者(低層コンポーネント)が呼び出される -> 実行者が内部メソッドを呼び出す(リクエストが実行される)。同様に、実行者はリクエスト送信者が誰なのか全く知らず、呼び出し者さえもはっきりしませんが、問題ありません。実行者は自分の本分をきちんと果たせばよく、リーダーの状況を知る必要はないのです。

3.呼び出し者(システム)の視点から

リクエストを受け取る -> コマンドオブジェクトを作成してリクエストをカプセル化 -> 適切なタイミングでコマンドオブジェクトのアクションを呼び出してリクエストを実行する(リクエストが実行される)。呼び出し者は実行者が誰なのか知らず、リクエスト送信者も分からず、コマンドを構築してコマンドの実行を制御する責任だけを負います。これで十分です。

上記から、各オブジェクト間の低結合関係が見て取れます:

リクエスト送信者(クライアント)と実行者(低層コンポーネント)は完全に結合が解かれ、仲介者である呼び出し者もリクエスト者と実行者の具体的な詳細を理解しておらず、それらはうまく保護されています。这正是我们想要的。

二.例を挙げる

現実世界のわずかに複雑なサブシステムにはすべて一連のコマンドが存在するはずです。例えばレストランの运行机制:

顧客 A がレストランに来て麺を 1 杯注文する(リクエストを送信) -> カウンター係が記録する(コマンドを作成) -> 係が厨房に注文票を投げる -> 料理人 C がすぐに麺を 1 杯完成させる(リクエストが実行される)。顧客は誰がこの麺を作るのか知らず、カウンター係も知らず、料理人は誰が麺を注文したのか知らず、麺を作れば休めることだけを知っています。コマンドパターンととても似ていませんか?

上記の机制をコードで実装してみましょう。まず、コマンドインターフェースが必要です。コマンドこそがコマンドパターンの核心であり、コマンドがなければすべて空想です

package CommandPattern;

/**

  • @author ayqy
  • 定义 Command 接口 */ public interface Command { public abstract void execute();//只需要定义一个统一的执行方法 }

コマンドがあれば実行者も必要です。そうでなければ将軍だけいて兵卒がおらず、レストランの実行者はもちろん料理人です:

package CommandPattern;

/**

  • @author ayqy

  • 定义 Chef 基类 */ public abstract class Chef { //在此定义厨师的公共属性

    /**

    • 定义烹饪方法 */ public abstract void cook(); //在此定义其它有用的方法 }

具体的な料理人も実装する必要があります。専門家に専門の技:

麺を作る料理人:

package CommandPattern;

/**

  • @author ayqy

  • 定义专业做面的厨师 */ public class NoodlesChef extends Chef{

    @Override public void cook() { System.out.println("做好了一碗美味的拉面"); } }

餅を作る料理人:

package CommandPattern;

/**

  • @author ayqy

  • 定义专业做饼的厨师 */ public class PieChef extends Chef{

    @Override public void cook() { System.out.println("做好了一块香喷喷的大饼"); } }

準備が整ったので、レストランを開業できます

三.効果示例

Test クラスが必要です:

package CommandPattern;

/**

  • @author ayqy

  • 实现测试类 */ public class Test {

    public static void main(String[] args) { System.out.println("Command Pattern 餐馆开张。。"); System.out.println("第一位客户 X 先生"); System.out.println("X 先生:你好,我需要一碗面,我饿极了"); NoodlesCommand nCmd = new NoodlesCommand(); System.out.println("柜台服务员:好的,我已经记下了,马上就好"); System.out.println("柜台服务员:厨房~~,接单"); nCmd.execute(); System.out.println("X 先生:真快啊!");

     System.out.println();
     
     System.out.println("第二位客户 XX 先生");
     System.out.println("XX 先生:你好,我需要一块饼,20 分钟后来取");
     PieCommand pCmd = new PieCommand();
     System.out.println("柜台服务员:好的,我已经记下了");
     System.out.println("15 分钟后");
     System.out.println("柜台服务员:厨房~~,接单");
     pCmd.execute();
     System.out.println("XX 先生:真准时啊!");
    

    } }

結果示例:


例から見て取れます:

  1. 呼び出し者(カウンター係)は具体的な実行タイミングを制御できますが、具体的な実行者(料理人)の詳細は全く分かりません
  2. リクエスト送信者(顧客)はレストランの运行机制を全く知らず、注文した料理が料理人が作ったのか、サービス係が作ったのか、隣から買ったのか分かりません。。
  3. 実行者(料理人)はリクエスト送信者の状況を全く知らず、自分の本分だけを果たし、他は何も知りません

四.コマンドパターンの拡張

1.マクロコマンド(複数のコマンドを順次実行)

「コマンドのコマンド」を定義して実装できます(このような特殊なコマンドの execute メソッド内部では、他のいくつかのコマンドの execute メソッドを順次呼び出します。。)

2.取り消し

多くの顧客が来て多くの料理を注文し、しばらくして数人の顧客が待てなくなって取り消しが必要になった場合、どのように実装しますか?コマンドリストを維持し、すでに作成されたコマンドを記録します。取り消す際には対応するコマンドを見つけ、取り消し操作を実行する必要があります。もちろん、前提としてコマンドオブジェクトが取り消しをサポートしている必要があり、いくつかの修正を行う必要があります:

package CommandPattern;

/**

  • @author ayqy

  • 定义 Command 接口 */ public interface Command { public abstract void execute();//只需要定义一个统一的执行方法

    public abstract void undo();//定义统一的撤销方法 }

各コマンドの取り消し操作は異なる可能性があるため、抽象メソッドとして定義し、サブクラスが具体的な操作を実装します

*どのように複数ステップの順次取り消しをサポートしますか?レストランの例ではそのような機能は必要ないかもしれませんが、別のシナリオを考えてみましょう。テキストエディタ:ユーザーが一連のコマンドを発行し、一連の操作を完了しましたが、後でそのような操作は必要なかったことに気づき、ユーザーは変更を取り消します(Ctrl + Z)。この場合、内容を復元するために逆の操作を実行する必要があります。どのように実装しますか?

やはり各コマンドの undo 動作を実装する必要があります(execute と逆順の操作を実行すればよい)。それ以外にも、初期状態への取り消しをサポートするために、すでに実行された操作を記録するスタックが必要です

3.キューリクエスト

ワーカースレッドを 1 つ作成し、すべての演算を担当させることができます。1 つのチャンネルを想像してください。入力はさまざまなコマンドで、出力はコマンドの実行結果です。おそらく一瞬前にワーカースレッドが大餅を作っていたのに、次の瞬間には买菜に行っています。。

*このようにするメリットは何ですか?演算を指定された数個のスレッドに制限し、制御できます

4.ログリクエスト

主にデータベース管理システムの実装に使用されます。一連の操作を記録する必要があります(例えばハードディスクに書き込む)。システム障害に遭遇した際に読み出してデータを復元します。どのように実装しますか?オブジェクトの直列化を使用してオブジェクトを保存し(ログを記録)、必要な際に逆直列化します(トランザクションを復元)

五.まとめ

コマンドパターンは効果的にリクエスト送信者と実行者の結合を解き、追加のメリット(例えば取り消し操作のサポート、キューリクエスト、ログ記録など)も提供できます

コメント

コメントはまだありません

コメントを書く