[12] デザインパターン ~~~ 動作パターン ~~~ コマンドパターン (Java)

コマンドパターン - コマンドパターン【学習難易度:★★★☆☆、使用頻度:★★★★☆】

1.1. パターンの動機付け

        ソフトウェア設計では、いくつかのオブジェクトにリクエストを送信する必要があることがよくありますが、リクエストの受信者が誰であるか、どの操作が要求されているかがわかりません。プログラムの実行時に特定のリクエストの受信者を指定するだけで十分です。はい、この時点でコマンドモードを使用して設計することで、リクエスト送信者とリクエスト受信者間の結合を排除し、オブジェクト間の呼び出し関係をより柔軟にすることができます。
        コマンド モードでは、送信者と受信者を完全に切り離すことができます。送信者と受信者の間には直接の参照関係はありません。リクエストを送信するオブジェクトは、リクエストの送信方法を知る必要があるだけで、リクエストを完了する方法を知る必要はありません。リクエスト。これがコマンドパターンのパターン動機です。

ケース1

        新しい家の装飾の最後のステップの 1 つは、照明や換気扇などの電気製品のオン/オフに使用できるソケットとスイッチの設置です。スイッチを購入するときは、どの電化製品を制御するのかわかりません。つまり、スイッチは照明や換気扇とは直接関係ありません。スイッチは、設置後に照明を制御するために使用される可能性があります。排気ファンやその他の電気機器の制御に使用される場合があります。スイッチと電化製品の間はワイヤーで接続されており、スイッチがオンの場合、ワイヤーに通電されて電化製品が動作しますが、そうでない場合、スイッチがオフになり、ワイヤーの電源がオフになり、電化製品は動作を停止します。図 1 に示すように、同じスイッチで異なるワイヤを介して異なるアプライアンスを制御できます。
ここに画像の説明を挿入

        図 1 では、スイッチはユーザーが「照明をつけて」リクエストを送信するリクエスト送信者として理解でき、ライトは「照明をつけて」リクエストの最終受信者および処理者です。スイッチ ランプとは直接の結合関係はなく、配線で接続されており、異なる要求受信者は異なる配線を使用して接続でき、1本の配線を変更するだけで、同じ送信者(スイッチ)が異なる受信者に対応できます。 )。
        ソフトウェア開発では、スイッチや電化製品と同様のリクエストの送信者オブジェクトと受信者オブジェクトも多数存在します。たとえば、ボタンが「ウィンドウを閉じる」リクエストの送信者となり、ボタンのクリック イベント処理クラスがリクエストの受信者となることがあります。 .によって。システムの結合を減らし、リクエストの送信者と受信者を分離するために、コマンド モードと呼ばれる設計パターンを使用してシステムを設計できます。コマンド モードでは、送信者と受信者が導入された新しいコマンド オブジェクト (同様のコマンド モード)図 1 のワイヤに接続すると、送信者のリクエストがコマンド オブジェクトにカプセル化され、コマンド オブジェクトを通じて受信者のメソッドが呼び出されます。この章では、リクエストの送信者と受信者を分離するためのコマンド パターンを学習します。

ケース 2 (カスタムファンクションキー)

        Sunny Software Company の開発者は、社内 OA システム用のデスクトップ アプリケーションを開発しました。このアプリケーションはユーザーに一連のカスタム ファンクション キーを提供し、ユーザーはこれらのファンクション キーを使用していくつかのショートカット操作を実行できます。Sunny Software Company の開発者は、分析を通じて、ユーザーによって使用習慣が異なる可能性があることを発見しました。ファンクション キーを設定する際には、人それぞれ好みがあります。たとえば、最初のファンクション キーを「ヘルプを開く」ドキュメントとして設定することを好む人もいます。ユーザーはファンクション キーを「トレイに最小化」に設定することを好みますが、ユーザーがファンクション キーを柔軟に設定できるようにするために、開発者は「ファンクション キー設定」ウィンドウを提供しており、そのインターフェイスは図 2 に示されています。
ここに画像の説明を挿入

        図 2 に示すインターフェイスを通じて、ユーザーはファンクション キーと対応する機能をバインドし、必要に応じてファンクション キーの設定を変更できます。システムは将来、新しい機能やファンクション キーを追加する可能性があります。

        Sunny Software Company の開発者は、次のコードを使用して、ファンクション キーと関数処理クラスの間の呼び出し関係を実現する予定です。

//FunctionButton:功能键类,请求发送者
class FunctionButton {
    
    
	private HelpHandler help; //HelpHandler:帮助文档处理类,请求接收者
	
    //在FunctionButton的onClick()方法中调用HelpHandler的display()方法
public void onClick() {
    
    
		help = new HelpHandler();
		help.display(); //显示帮助文档
	}
}

        上記のコードでは、ファンクション キー クラス FunctionButton がリクエストの送信者として機能し、ヘルプ ドキュメント処理クラス HelpHandler がリクエストの受信者として機能します。送信者の FunctionButton の onClick() メソッドでは、display() メソッドが使用されます。受信側の HelpHandler が呼び出されます。明らかに、上記のコードを使用すると、システムに次の問題が発生します。

  • (1) リクエスト送信者とリクエスト受信者間で直接メソッド呼び出しが存在するため、結合度が非常に高く、リクエスト受信者を置き換えるには送信者のソースコードを変更する必要がある。レシーバー HelpHandler を WindowHanlder (ウィンドウ処理クラス) に変更するには、FunctionButton のソース コードを変更する必要がありますが、これは「開閉の原則」に違反します。
  • (2) FunctionButton クラスの機能は設計時および実装時に固定されているため、元の FunctionButton クラスを変更せずに新たなリクエストレシーバを追加する場合は、FunctionButton と同様の機能を持つ新しいクラスを追加する必要があります。このシステムにより、中間層の数が劇的に増加します。リクエストの受信側である HelpHandler、WindowHanlder、およびその他のクラスの間には関係がない可能性があり、共通の抽象化層がないため、「依存関係逆転の原則」に従って FunctionButton を設計することは困難です。
  • (3) ファンクションキーの機能をユーザーが必要に応じて設定できない ファンクションキーの機能が固定されてしまうと、ソースコードを変更しないと機能を変更できず、システムの柔軟性に欠けます。

        これらすべての問題が、リクエスト送信側の FunctionButton クラスとリクエスト受信側の HelpHandler や WindowHanlder などのクラス間の直接的な結合関係によって引き起こされていることを知るのは難しくありません。送信者は異なる受信者に対応できますか? これは、Sunny Software Company の開発者が「ファンクション キー設定」モジュールを設計する際に考慮しなければならない問題です。このような問題を解決するためにコマンドモードが生まれましたが、現時点ではコマンドモードを利用すれば上記の問題はある程度解決できます(注:クラス数の増加の問題はコマンドモードでは解決できません)。

1.2. スキーマ定義

        コマンド パターン: リクエストをオブジェクトとしてカプセル化し、さまざまなリクエストを持つ顧客をパラメータ化できるようにします。リクエストをキューに入れたり、リクエスト ログを記録したり、取り消し可能な操作をサポートしたりできます。コマンドモードはオブジェクトの動作モードであり、その別名はアクション (Action) モードまたはトランザクション (Transaction) モードです。

1.3. スキーマ構造

コマンド モードには次の役割が含まれます。

  • コマンド: 抽象コマンド クラス抽象コマンド クラスは通常、リクエストを実行するためのexecute() などのメソッドが宣言され、リクエスト受信側の関連操作をこれらのメソッドを通じて呼び出すことができる抽象クラスまたはインターフェイスです。
  • ConcreteCommand: 具体的なコマンド クラス。具体的なコマンド クラスは抽象的なコマンド クラスのサブクラスであり、抽象的なコマンド クラスで宣言されたメソッドを実装します。このメソッドは、特定のレシーバー オブジェクトに対応し、レシーバー オブジェクトのアクションをそれにバインドします。execute()メソッドを実装すると、受信側オブジェクトの関連する操作(Action)が呼び出されます。
  • 呼び出し者: Invoker。呼び出し元はリクエストの送信者であり、コマンド オブジェクトを通じてリクエストを実行します。呼び出し側は設計時に受信側を決定する必要がないため、抽象コマンド クラスとの関連付け関係のみを持ちます。プログラムの実行中に、特定のコマンド オブジェクトをプログラムに挿入し、その特定のコマンド オブジェクトのexecute() メソッドを呼び出して、リクエスト レシーバーを間接的に呼び出す関連操作を実現できます。
  • 受信者: 受信者。受信者はリクエストに関連する操作を実行し、具体的にはリクエストのビジネス処理を実装します。
    ここに画像の説明を挿入

        コマンド モードの本質はリクエストをカプセル化することであり、リクエストはコマンドに対応し、コマンドを発行する責任はコマンドを実行する責任から分離されます各コマンドは操作です。要求側は操作を実行する要求を送信し、受信側は要求を受信して​​対応する操作を実行します。コマンド モードでは、要求側と受信側を独立させることができるため、要求側は受信側のインターフェイスを知る必要がありません。さらに、要求がどのように受信されるか、操作が実行されるかどうか、いつ実行されるかなどを知る必要もありません。 、およびその実行方法
         コマンドモードのポイントは抽象コマンドクラスの導入であり、リクエスト送信側プログラムは抽象コマンドクラス用であり、リクエスト受信側には抽象コマンドクラスを実装した特定のコマンドのみが関連付けられる最も単純な抽象コマンド クラスには、抽象execute() メソッドのみが含まれます。各特定のコマンド クラスは、Receiver タイプのオブジェクトをインスタンス変数として格納し、それによってリクエスト レシーバーを指定します。さまざまな特定のコマンド このクラスは、execute() メソッドとメソッドのさまざまな実装を提供します。さまざまな受信者のリクエスト処理メソッドを呼び出します。

典型的な抽象コマンド クラスのコードは次のとおりです。

abstract class Command {
    
    
	public abstract void execute();
}

        リクエストの送信者、つまり呼び出し側では、抽象コマンド クラスに対してプログラミングが実行され、実行時にコンストラクション インジェクションまたは設定値インジェクションと、コマンド オブジェクトはビジネス メソッド メソッド内で呼び出すことができます。その一般的なコードは次のとおりです。

class Invoker {
    
    
	private Command command;
	
    //构造注入
	public Invoker(Command command) {
    
    
		this.command = command;
	}
	
    //设值注入
	public void setCommand(Command command) {
    
    
		this.command = command;
	}
	
	//业务方法,用于调用命令类的execute()方法
	public void call() {
    
    
		command.execute();
	}
}

        特定コマンドクラスは、リクエスト受信側に関連付けられた抽象コマンドクラスを継承し、抽象コマンドクラス内で宣言されたexecute()メソッドを実装し、実装時に受信側のリクエスト応答メソッドaction()を呼び出します。以下のように表示されます:

class ConcreteCommand extends Command {
    
    
	private Receiver receiver; //维持一个对请求接收者对象的引用
 
	public void execute() {
    
    
		receiver.action(); //调用请求接收者的业务处理方法action()
	}
}

        リクエスト受信側の Receiver クラスは、リクエストのビジネス処理を具体的に実装します。リクエストに関連する操作を実行するための action() メソッドを提供します。一般的なコードは次のとおりです。

class Receiver {
    
    
	public void action() {
    
    
		//具体操作
	}
}

1.4. タイミング図

ここに画像の説明を挿入

1.5. コード分析

        ファンクション キーとファンクション処理クラスの結合を減らし、ユーザーが各ファンクション キーの機能をカスタマイズできるようにするために、Sunny ソフトウェア会社の開発者はコマンド モードを使用して「カスタム ファンクション キー」モジュールを設計しました。そのコア構造は次のとおりです。図 4 に示されているのは次のとおりです。

1.5.1 生産

        図 4 では、FBSettingWindow は「ファンクション キー設定」インターフェイス クラス、FunctionButton はリクエストの呼び出し側として機能し、Command は抽象コマンド クラスとして機能し、MinimizeCommand と HelpCommand は特定のコマンド クラスとして機能し、WindowHanlder と HelpHandler はリクエストの受信側として機能します。完全なコードは次のとおりです。

package com.zyz.demo;

import java.util.ArrayList;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/25 18:24
 * @Description:
 */

//功能键设置窗口类

class FBSettingWindow {
    
    
    private String title; //窗口标题
    /**
     * 定义一个ArrayList来存储所有功能键
     */
    private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();

    public FBSettingWindow(String title) {
    
    
        this.title = title;
    }

    public void setTitle(String title) {
    
    
        this.title = title;
    }

    public String getTitle() {
    
    
        return this.title;
    }

    public void addFunctionButton(FunctionButton fb) {
    
    
        functionButtons.add(fb);
    }

    public void removeFunctionButton(FunctionButton fb) {
    
    
        functionButtons.remove(fb);
    }


    /**
     * 显示窗口及功能键
     */
    public void display() {
    
    
        System.out.println("显示窗口:" + this.title);
        System.out.println("显示功能键:");
        for (Object obj : functionButtons) {
    
    
            System.out.println(((FunctionButton) obj).getName());
        }
        System.out.println("------------------------------");
    }

}


//功能键类:请求发送者

class FunctionButton {
    
    
    private String name; //功能键名称
    private Command command; //维持一个抽象命令对象的引用

    public FunctionButton(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return this.name;
    }

    /**
     * 为功能键注入命令
     *
     * @param command
     */
    public void setCommand(Command command) {
    
    
        this.command = command;
    }

    /**
     * 发送请求的方法
     */
    public void onClick() {
    
    
        System.out.print("点击功能键盘:");
        command.execute();
    }


}

//抽象命令类

abstract class Command {
    
    
    public abstract void execute();
}


//帮助命令类:具体命令类

class HelpCommand extends Command {
    
    
    private HelpHandler hhObj; //维持对请求接收者的引用

    public HelpCommand() {
    
    
        hhObj = new HelpHandler();
    }

    /**
     * 命令执行方法,将调用请求接收者的业务方法
     */
    @Override
    public void execute() {
    
    
        hhObj.display();
    }
}


//最小化命令类:具体命令类

class MinimizeCommand extends Command {
    
    
    private WindowHanlder whObj; //维持对请求接收者的引用

    public MinimizeCommand() {
    
    
        whObj = new WindowHanlder();
    }

    /**
     * 命令执行方法,将调用请求接收者的业务方法
     */
    @Override
    public void execute() {
    
    
        whObj.minimize();
    }
}


//窗口处理类:请求接收者

class WindowHanlder {
    
    
    public void minimize() {
    
    
        System.out.println("将窗口最小化至托盘!");
    }
}

//帮助文档处理类:请求接收者

class HelpHandler {
    
    
    public void display() {
    
    
        System.out.println("显示帮助文档!");
    }
}

1.5.2 クライアント

次のクライアント テスト コードを作成します。

package com.zyz.demo;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/5/25 19:09
 * @Description:
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        FBSettingWindow fbsw = new FBSettingWindow("功能键设置");

        FunctionButton fb1,fb2;
        fb1 = new FunctionButton("功能键1");
        fb2 = new FunctionButton("功能键1");

        //生成具体命令对象
        Command command1,command2;
        command1 = new MinimizeCommand();
        command2 = new HelpCommand();

        //将命令对象注入功能键
        fb1.setCommand(command1);
        fb2.setCommand(command2);

        fbsw.addFunctionButton(fb1);
        fbsw.addFunctionButton(fb2);
        fbsw.display();

        //调用功能键的业务方法
        fb1.onClick();
        fb2.onClick();
    }
}

1.5.3 テスト結果

ここに画像の説明を挿入

1.6. パターン分析

        コマンド モードの本質は、コマンドをカプセル化し、コマンドを発行する責任とコマンドを実行する責任を分離することです。

  • 各コマンドは操作です。要求側は操作を実行する要求を送信し、受信側は要求を受信して​​操作を実行します。
  • コマンド モードでは、要求側と受信側を独立させることができるため、要求側は受信側のインターフェイスを知る必要がありません。さらに、要求がどのように受信されるか、操作が実行されるかどうか、いつ実行されるかなどを知る必要もありません。 、そしてどのように実行されるか。
  • コマンド パターンにより、リクエスト自体が他のオブジェクトと同様に保存および受け渡しできるオブジェクトになります。
  • コマンド モードの鍵は、抽象コマンド インターフェイスの導入と、抽象コマンド インターフェイス用の送信側プログラムであり、抽象コマンド インターフェイスを実装する特定のコマンドのみを受信側に関連付けることができます。

1.7. 例

例1:テレビのリモコン

  • テレビはリクエストの受信者であり、リモコンはリクエストの送信者であり、リモコンにはいくつかのボタンがあり、テレビのさまざまな操作に対応するボタンが異なります。抽象コマンドの役割は、コマンド インターフェイスによって実行されます。抽象コマンド インターフェイスを実装する 3 つの特定のコマンド クラスがあります。これらの 3 つの特定のコマンド クラスは、テレビの電源を入れる、テレビの電源を切る、およびチャンネルの切り替えという 3 つの操作を表します。明らかに、テレビのリモコンはコマンド モードの典型的な応用例です。
    ここに画像の説明を挿入

タイミング図
ここに画像の説明を挿入

1.8. 利点

コマンドパターンのメリット

  • システムの結合を減らします。
  • 新しいコマンドをシステムに簡単に追加できます。
  • コマンドキューやマクロコマンド(複合コマンド)の設計は比較的簡単です。
  • リクエストのUndoとRedoを簡単に実装できます。

1.9. 欠点

コマンドパターンのデメリット

  • コマンド パターンを使用すると、一部のシステムで具体的なコマンド クラスが多すぎる場合があります。コマンドごとに特定のコマンド クラスを設計する必要があるため、一部のシステムでは多数の特定のコマンド クラスが必要になる場合があり、これはコマンド モードの使用に影響します。

1.10. 適用環境

コマンド モードは次の場合に使用できます。

  • システムは、呼び出し元と受信者が直接対話しないように、リクエストの呼び出し元とリクエストの受信者を切り離す必要があります。
  • システムはリクエストを指定し、キューに入れて、異なる時間に実行する必要があります。
  • システムは、コマンドの元に戻す (Undo) 操作と回復 (Redo) 操作をサポートする必要があります。
  • システムは、一連の操作をグループ化する必要があります。つまり、マクロ コマンドをサポートする必要があります。

1.11. パターンの適用

        多くのシステムは、UNIX プラットフォームでのシェル プログラミングなどのマクロ コマンド機能を提供します。これは、複数のコマンドをコマンド オブジェクトにカプセル化でき、コマンド シーケンスを実行するのに単純なコマンドのみが必要です。これは、コマンド モードの応用例の 1 つでもあります。 . 1つ。

1.12. スキーマ拡張

マクロコマンドはコンビネーションコマンドとも呼ばれ、コマンドモードとコンビネーションモードを組み合わせたものです。

  • マクロ コマンドも特定のコマンドですが、他のコマンド オブジェクトへの参照が含まれています。マクロ コマンドのexecute() メソッドを呼び出すと、それに含まれる各メンバー コマンドのexecute() メソッドが再帰的に呼び出されます。オブジェクトは単純なコマンドにすることも、引き続きマクロ コマンドにすることもできます。マクロコマンドを実行すると、特定のコマンドを複数実行することにより、コマンドの一括処理を実現します。

1.13. 概要

  • コマンド モードでは、リクエストはオブジェクトとしてカプセル化されるため、さまざまなリクエストでクライアントをパラメータ化し、リクエストをキューに入れたり、リクエスト ログを記録したり、取り消し可能な操作をサポートしたりできます。コマンド モードはオブジェクト動作モードであり、その別名はアクション モードまたはトランザクション モードです。
  • コマンド モードには 4 つの役割が含まれます: 抽象コマンド クラスは、リクエストを実行するためのメソッド (execute() など) を宣言し、これを通じてリクエスト受信側の関連操作を呼び出すことができます。特定のコマンド クラスは抽象コマンド クラスのサブクラスであり、抽象コマンド クラスで宣言されたメソッドは、特定の受信者オブジェクトに対応し、受信者オブジェクトのアクションをそれにバインドします。呼び出し元はリクエストの送信者 (リクエスタとも呼ばれます) であり、コマンドを通じてリクエストを実行します。オブジェクト; 受信者 リクエストに関連する操作を実行し、具体的にはリクエストの業務処理を実現します。
  • コマンド モードの本質は、コマンドをカプセル化し、コマンドを発行する責任とコマンドを実行する責任を分離することです。コマンド パターンにより、リクエスト自体が他のオブジェクトと同様に保存および受け渡しできるオブジェクトになります。
  • コマンド モードの主な利点は、システムの結合度が減少し、新しいコマンドの追加が非常に便利で、コマンド キューとマクロ コマンドの設計が比較的簡単で、キャンセルとマクロ コマンドの実現が便利であることです。リクエストの復元。その主な欠点は、一部のシステムには具体的なコマンド クラスが多すぎるという問題が発生する可能性があることです。
  • コマンド モードのアプリケーションには、リクエストの呼び出し元とリクエストの受信者を分離して、呼び出し元と受信者が直接対話しないようにする必要があること、リクエストを指定し、リクエストをキューに入れて、異なる時間にリクエストを実行する必要があること、およびリクエストを指定する必要があること、およびリクエストを異なる時間に実行する必要があることなどが挙げられます。コマンドキャンセル操作をサポートする必要があり、操作を復元するには、一連の操作を組み合わせる必要があります。つまり、マクロコマンドがサポートされています。

1.14. コマンドキューの実装

        場合によっては、複数のリクエストをキューに入れる必要がある場合があります。リクエスト送信者がリクエストを送信すると、複数のリクエスト受信者が応答を生成し、これらのリクエスト受信者はビジネス メソッドを 1 つずつ実行してリクエストの処理を完了します。この時点では、コマンド キューを通じてこれを実現できます。

        コマンド キューを実装するにはさまざまな方法があります。最も一般的で柔軟な方法は、複数のコマンド オブジェクトを格納する役割を担う CommandQueue クラスを追加することです。異なるコマンド オブジェクトは異なる要求受信に対応できます。あるいは、CommandQueue の一般的なコードクラスは次のとおりです。

import java.util.*;
 
class CommandQueue {
    
    
    //定义一个ArrayList来存储命令队列
	private ArrayList<Command> commands = new ArrayList<Command>();
	
	public void addCommand(Command command) {
    
    
		commands.add(command);
	}
	
	public void removeCommand(Command command) {
    
    
		commands.remove(command);
	}
	
    //循环调用每一个命令对象的execute()方法
	public void execute() {
    
    
		for (Object command : commands) {
    
    
			((Command)command).execute();
		}
	}
}

        コマンド キュー クラス CommandQueue を追加した後、リクエスト送信側クラス Invoker が CommandQueue に対してプログラムを実行し、コードが次のように変更されます。

class Invoker {
    
    
	private CommandQueue commandQueue; //维持一个CommandQueue对象的引用
	
    //构造注入
	public Invoker(CommandQueue commandQueue) {
    
    
		this. commandQueue = commandQueue;
	}
	
    //设值注入
	public void setCommandQueue(CommandQueue commandQueue) {
    
    
		this.commandQueue = commandQueue;
	}
	
	//调用CommandQueue类的execute()方法
	public void call() {
    
    
		commandQueue.execute();
	}
}

        コマンド キューは、私たちがよく「バッチ処理」と呼ぶものに似ています。バッチ処理は、その名前が示すように、オブジェクト (コマンド) のグループをバッチ処理できます。送信者がリクエストを送信すると、一連の受信者がそのリクエストに応答します。コマンド キューを使用して、バッチ処理アプリケーションを設計できます。リクエストが送信された場合、レシーバー コマンドを受信する厳密な順序はありませんが、マルチスレッド技術を使用してコマンド オブジェクトのexecute() メソッドを同時に呼び出すこともでき、プログラムの実行効率を向上させることができます。

1.15. 元に戻す操作

        コマンドモードでは、コマンドオブジェクトのexecute()メソッドを呼び出すことでリクエストを処理できますが、リクエストをキャンセル(Undo)する必要がある場合は、コマンドクラスに逆の操作を追加することで実装できます。

拡張: アンドゥは、逆の操作で実現するだけでなく、オブジェクトの履歴状態を保存することでも実現できます。これは、メメントパターン (MementoPattern) を使用することで実現できます。

        Sunny Software Company は、単純な数学演算を実現でき、操作を元に戻すこともできる単純な計算機を開発する予定です。

        Sunny Software Company の開発者は、コマンド モードを使用して、図 5 に示す構造図を設計しました。この図では、計算機インターフェイス クラス CalculatorForm がリクエストの送信者として機能し、データ集計機能を実現する加算クラス Adder がリクエストの受信者として機能します。インターフェイス クラスは間接的に加算を呼び出すことができます。クラスの add() メソッドは加算操作を実装し、加算操作を取り消すことができる undo() メソッドを提供します。
ここに画像の説明を挿入

この例の完全なコードは次のとおりです。

//加法类:请求接收者
class Adder {
    
    
	private int num=0; //定义初始值为0
	
    //加法操作,每次将传入的值与num作加法运算,再将结果返回
	public int add(int value) {
    
    
		num += value;
		return num;
	}
}
 
//抽象命令类
abstract class AbstractCommand {
    
    
	public abstract int execute(int value); //声明命令执行方法execute()
	public abstract int undo(); //声明撤销方法undo()
}
 
//具体命令类
class ConcreteCommand extends AbstractCommand {
    
    
	private Adder adder = new Adder();
	private int value;
		
	//实现抽象命令类中声明的execute()方法,调用加法类的加法操作
public int execute(int value) {
    
    
		this.value=value;
		return adder.add(value);
	}
	
    //实现抽象命令类中声明的undo()方法,通过加一个相反数来实现加法的逆向操作
	public int undo() {
    
    
		return adder.add(-value);
	}
}
 
//计算器界面类:请求发送者
class CalculatorForm {
    
    
	private AbstractCommand command;
	
	public void setCommand(AbstractCommand command) {
    
    
		this.command = command;
	}
	
    //调用命令对象的execute()方法执行运算
	public void compute(int value) {
    
    
		int i = command.execute(value);
		System.out.println("执行运算,运算结果为:" + i);
	}
	
    //调用命令对象的undo()方法执行撤销
	public void undo() {
    
    
		int i = command.undo();
		System.out.println("执行撤销,运算结果为:" + i);
	}
}

编写如下客户端测试代码:
class Client {
    
    
	public static void main(String args[]) {
    
    
		CalculatorForm form = new CalculatorForm();
		AbstractCommand command;
		command = new ConcreteCommand();
		form.setCommand(command); //向发送者注入命令对象
		
		form.compute(10);
		form.compute(5);
		form.compute(10);
		form.undo();
	}
}

プログラムをコンパイルして実行すると、出力は次のようになります。
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_43304253/article/details/130985594