シーン
アプリケーションのデスクトップ バージョンを開発するために、アプリケーションはユーザーに一連のカスタム ファンクション キーを提供し、ユーザーはこれらのファンクション キーを使用していくつかのショートカット操作を実行できます。
ユーザーはファンクション キーと対応する機能をバインドでき、必要に応じてファンクション キーの設定を変更することもできます。システムは将来的に新しい機能やファンクション キーを追加する可能性があります。
これは、コマンド パターンを使用しない場合に可能です。
送信側 FunctionButton の onClick() で、ファンクション キー クラス FunctionButton がリクエストの送信者として機能し、ヘルプ ドキュメント処理クラス HelpHandler がリクエストの受信者として機能します。
メソッドは、受信側 HelpHandler の display() メソッドを呼び出します。
機能ボタン:
public class FunctionButton {
//帮助文档处理类,请求接收者
private HelpHandler helperHandler;
public void onClick(){
helperHandler = new HelpHandler();
//显示帮助文档
helperHandler.display();
}
}
ヘルプハンドラ:
public class HelpHandler {
public void display(){
System.out.println("显示帮助文档");
}
}
上記の問題:
(1) リクエスト送信者とリクエスト受信者間で直接メソッド呼び出しがあるため、結合度が非常に高く、リクエスト受信者を置き換えるには送信者のソースコードを変更する必要があります。
リクエスト受信側の HelpHandler を WindowHanlder (ウィンドウ処理クラス) に変更する必要がある場合は、FunctionButton のソース コードを変更する必要があり、「開閉の原則」に違反します。
(2) FunctionButton クラスの機能は設計・実装時に修正されており、新たなリクエスト受信先を追加した場合、元の FunctionButton クラスを変更しないと、
次に、FunctionButton に似た新しいクラスを追加する必要があり、システム内のクラスの数が急激に増加します。
リクエスト受信側の HelpHandler、WindowHanlder、および他のクラスの間には何の関係もない可能性があるため、共通の抽象化層がありません。
したがって、FunctionButton を「依存関係逆転の原則」に従って設計することも困難です。
(3) ファンクションキーの機能はユーザーが独自に設定することができず、一度ファンクションキーの機能が固定されると、ソースコードを変更しない限りその機能を変更することはできません。
システムには柔軟性がありません。
コマンドパターンの概要
ソフトウェア開発では、多くの場合、いくつかのオブジェクトにリクエストを送信する (1 つまたは複数のメソッドを呼び出す) 必要がありますが、リクエストの受信者が誰であるかはわかりません。
どの操作が要求されているかわかりません。現時点では、特に、要求の送信者と要求の受信者が疎結合になるようにソフトウェアを設計したいと考えています。
相互の結合を排除し、オブジェクト間の呼び出し関係をより柔軟にし、要求の受信者や要求する操作を柔軟に指定できます。
コマンド モードは、このような問題に対するより完璧な解決策を提供します。
コマンド モードでは、リクエストの送信者と受信者を完全に分離できます。送信者と受信者の間には直接の参照関係はありません。リクエストを送信するオブジェクトは、リクエストの送信方法を知っていればよいだけです。
リクエストを完了する方法を知らなくても。
コマンドパターン:
リクエストをオブジェクトとしてカプセル化すると、さまざまなリクエストでクライアントをパラメータ化でき、リクエストをキューに入れたり、リクエスト ログを記録したり、取り消し可能な操作をサポートしたりできます。
コマンドモードはオブジェクトの動作モードであり、その別名はアクション (Action) モードまたはトランザクション (Transaction) モードです。
コマンドモード構成図
コマンド モードに含まれる役割は次のとおりです。
コマンド (抽象コマンドクラス):
抽象コマンド クラスは通常、リクエストを実行するためのexecute() などのメソッドが宣言された抽象クラスまたはインターフェイスです。
これらのメソッドを通じて、リクエスト受信側の関連操作を呼び出すことができます。
ConcreteCommand (特定のコマンド クラス):
具象コマンドクラスは、抽象コマンドクラスのサブクラスであり、抽象コマンドクラスで宣言されたメソッドを実装し、特定の受信オブジェクトに対応します。
受信側オブジェクトのアクションをそれにバインドします。execute()メソッドを実装すると、受信側オブジェクトの関連する操作(Action)が呼び出されます。
呼び出し元 (呼び出し元):
呼び出し元はリクエストの送信者であり、コマンド オブジェクトを通じてリクエストを実行します。呼び出し側は設計時に受信側を決定する必要はありません。
したがって、これは抽象コマンド クラスにのみ関連付けられます。実行中のプログラムに特定のコマンド オブジェクトを挿入できます。
次に、特定のコマンド オブジェクトのexecute() メソッドを呼び出して、間接呼び出し受信側の関連操作を実現します。
受信機:
受信者はリクエストに関連する操作を実行し、リクエストの業務処理を実装します。コマンド モードの本質は、リクエストをカプセル化することです。
リクエストはコマンドに対応し、コマンドを発行する責任とコマンドを実行する責任を分離します。すべてのコマンドは操作です。
要求側は操作を実行するリクエストを送信し、受信側はそのリクエストを受信して、対応する操作を実行します。
コマンドモードでは、要求側と受信側が独立しているため、要求側は受信側のインターフェイスを知る必要がありません。
リクエストがどのように受信されるか、操作が実行されるかどうか、いつ、どのように実行されるかを知る必要はありません。
注記:
ブログ:
Domineering Rogue Temperament_C#、アーキテクチャ ロード、SpringBoot-CSDN ブログ
成し遂げる
上記のユーザー定義ファンクション キーを実装するには、コマンド モードを使用します。
1. 新しい抽象コマンド クラスを作成します。
//抽象命令类
abstract class Command {
public abstract void execute();
}
2. 新しい特定のコマンド クラスを作成します: help コマンド クラス
//帮助命令类:具体命令类
public class HelpCommand extends Command{
//维持对请求接收者的引用
private HelpHandler helpHandler;
public HelpCommand(){
helpHandler = new HelpHandler();
}
//命令执行方法,将调用请求接收者的业务方法
public void execute() {
helpHandler.display();
}
}
リクエストの受信者への参照を維持します。
3. 新しいリクエスト受信者とヘルプ文書処理クラスを作成します。
//帮助文档处理类:请求接受者
public class HelpHandler {
public void display(){
System.out.println("显示帮助文档");
}
}
4. 同じ方法で新しい特定のコマンド クラスを作成します。コマンド クラスを最小化します。
//最小化命令类:具体命令类
public class MinimizeCommand extends Command{
//维持对请求接收者的引用
private WindowHandler windowHandler;
public MinimizeCommand(){
windowHandler = new WindowHandler();
}
//命令执行方法,将调用请求接收者的业务方法
public void execute() {
windowHandler.minimize();
}
}
リクエスト受信者の最小化されたウィンドウ処理クラスへの参照を維持します。
5. 新しい最小化ウィンドウ処理クラスを作成します。
//窗口处理类:请求接收者
public class WindowHandler {
public void minimize(){
System.out.println("将窗口最小化");
}
}
6. 新しいリクエスト送信者: ファンクション キー クラス
//功能键类:请求发送者
public class FunctionButton {
//功能键名称
private String name;
//维持一个抽象命令对象的引用
private Command command;
public FunctionButton(String name) {
this.name = name;
}
public String getName(){
return this.name;
}
//为功能键注入命令
public void setCommand(Command command){
this.command = command;
}
public void onClick(){
System.out.println("点击功能键:");
command.execute();
}
}
7. 新しいファンクションキーを作成してウィンドウクラスを設定します
import java.util.ArrayList;
//功能键设置窗口类
public class FBSettingWindow {
//窗口标题
private String title;
//定义一个ArrayList来存储所有功能键
private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();
public FBSettingWindow(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = 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("-------------------------");
}
}
8. クライアント呼び出し方法
public class Client {
public static void main(String[] args) {
FBSettingWindow fbsw = new FBSettingWindow("功能键设置");
FunctionButton fb1,fb2;
fb1 = new FunctionButton("功能键1");
fb2 = new FunctionButton("功能键2");
Command command1,command2;
//通过读取配置文件或其它方式生成具体命令对象
command1 = new HelpCommand();
command2 = new MinimizeCommand();
//将命令对象注入功能键
fb1.setCommand(command1);
fb2.setCommand(command2);
fbsw.addFunctionButton(fb1);
fbsw.addFunctionButton(fb2);
fbsw.display();
//调用功能键的业务方法
fb1.onClick();
fb2.onClick();
}
}
9. まとめ
ファンクション キーの機能を変更する必要がある場合、たとえば、特定のファンクション キーで「自動スクリーン キャプチャ」を実現できる場合は、それに対応して新しい特定のコマンド クラスを追加するだけで済みます。
コマンド クラスとスクリーン ハンドラー (ScreenHandler) の間に関連付け関係を作成し、設定ファイルを通じて特定のコマンド クラスのオブジェクトをファンクション キーに挿入します。
元のコードを変更する必要はなく、「オープンとクローズの原則」に準拠しています。このプロセスでは、特定の各コマンド クラスがリクエスト プロセッサ (受信者) に対応し、
リクエストの送信側に異なる特定のコマンド オブジェクトを挿入することで、同じ送信側が異なる受信側に対応できるようになり、「リクエストをオブジェクトにカプセル化し、
さまざまなリクエストでクライアントをパラメータ化する」では、クライアントはリクエストの受信側を直接操作することなく、特定のコマンド オブジェクトをパラメータとしてリクエストの送信側に挿入するだけで済みます。
コマンド パターンの主な利点は次のとおりです。
(1) システムの結合度を下げる。リクエスタとレシーバの間には直接の参照がないため、リクエスタとレシーバは完全に分離されています。
同じリクエスタが異なるレシーバに対応でき、同じレシーバを異なるリクエスタが使用することもでき、両者の間には十分な独立性があります。
(2) 新しいコマンドをシステムに簡単に追加できます。新しい特定のコマンド クラスを追加しても他のクラスに影響を与えないため、新しい特定のコマンド クラスの追加は簡単です。
「オープンとクローズの原則」の要件を満たすために、元のシステムのソース コードを変更する必要はなく、さらにはクライアント クラス コードを変更する必要もありません。
(3) コマンドキューやマクロコマンド(複合コマンド)の設計が比較的容易です。
(4) 要求された「元に戻す」および「やり直し」操作の設計および実装スキームを提供します。
コマンド パターンの主な欠点は次のとおりです。
コマンド パターンを使用すると、一部のシステムで具体的なコマンド クラスが多すぎる場合があります。リクエスト受信側への呼び出し操作ごとに特定のコマンドクラスを設計する必要があるため、
したがって、一部のシステムでは、コマンド モードの使用に影響を与える多数の特定のコマンド クラスを提供する必要がある場合があります。
該当シーン
次の場合は、コマンド パターンの使用を検討してください。
(1) システムは、呼び出し元と受信者が直接対話しないように、リクエストの呼び出し元とリクエストの受信者を切り離す必要があります。リクエストの呼び出し元は受信者の存在を知る必要も、受信者が誰であるかを知る必要もありません。
受信側も、いつ呼び出されるかを気にする必要はありません。
(2) システムは、リクエストを指定し、リクエストをキューに入れ、異なる時間にリクエストを実行する必要があります。コマンド オブジェクトとリクエストの元の呼び出し元の有効期間は異なる場合があります。
言い換えれば、元のリクエスト送信者はもう存在しない可能性がありますが、コマンド オブジェクト自体はまだアクティブであり、コマンド オブジェクトを通じてリクエスト受信者を呼び出すことができます。
リクエストの呼び出し元の存在を気にする代わりに、ログ ファイルをリクエストするなどのメカニズムを通じて実装できます。
(3) システムは、コマンドの Undo および Redo 操作をサポートする必要があります。
(4) システムは、一連の操作を組み合わせてマクロ コマンドを形成する必要があります。