「コマンドデザインパターン」:コードの制御と柔軟性を高め、ユーザー操作の簡素化、元に戻し/やり直し、そしてそれ以上の機能!
この記事では、行動デザインパターンのシリーズを続けていきます。今こそ 「コマンドパターン」 (1.).
コマンド設計パターンは、リクエストの送信側と受信側を切り離す問題に対処する行動設計パターンです。これはリクエストをオブジェクトとしてカプセル化し、クライアントが異なるリクエストでパラメータ化したり、キューやログリクエストを行ったり、実行不能な操作をサポートしたりできるようにします。このパターンはソフトウェアシステムの柔軟性、保守性、拡張性を促進し、オブジェクト間の複雑な相互作用の管理を容易にします。
コマンドパターンは特に有用です (私は必須だと言いたいです) ユーザー操作を柔軟に管理し、複雑なやり取りの管理を簡素化する必要がある多くの状況においてもそうです。
では、状況を明確にする例からすぐに始めましょう。
例として考えてみましょう 「ユーザーインターフェースツールキット」.UIツールキットには、ユーザー入力に応じてリクエストを実行するボタンやメニューなどのオブジェクトが含まれています。しかし、ツールキットはボタンやメニューでリクエストを明示的に実装できません。なぜなら、ツールキットを使うアプリケーションだけがどのオブジェクトで何をすべきかを知っているからです。ツールキットの設計者として、依頼の受け手やそれを実行する操作を知る手段はありません。
コマンドモデルは、GUIオブジェクトがこれらのリクエストを直接提出すべきではないことを示唆しています。代わりに、呼び出しされたオブジェクト、メソッド名、引数のリストなど、すべてのリクエストの詳細を別のコマンドクラスで単一のメソッドで抽出し、このリクエストをトリガーします。
コマンドオブジェクトは、さまざまなGUIやビジネスロジックオブジェクト間のリンクとして機能します。今後、GUIオブジェクトはどのビジネスロジックオブジェクトがリクエストを受け取り、どのように処理されるかを知る必要はありません。GUIオブジェクトはコマンドのみを起動し、そのコマンドがすべての詳細を処理します。
このパターンの鍵は抽象です 指揮 これは操作を実行するためのインターフェースを宣言します。最も単純な形では、このインターフェースには抽象的な「実行」 手術。
コンクリートコマンド サブクラスは、受信側をインスタンス変数として格納し、 を実装することで受信側とアクションペアを指定します 実行 その要請を発動するために。
受取人はその要求を実行するために必要な知識を持っています。次の例を見てみましょう。
その メニュー コマンドオブジェクトで簡単に実装可能です。メニュー内の各選択肢は メニュー項目 品格。あ 応用 クラスはメニューを作成し、 メニュー項目.アプリケーションクラスはまた、どのクラスかも記録します 文書 ユーザーが開いたオブジェクト。
アプリケーションはそれぞれを設定します メニュー項目 コンクリートのインスタンスを含みます 指揮 サブクラス。ユーザーが メニュー項目、メニュー項目は実行」 関連するコマンドに対してメソッドを操作し、操作を実行します。
下に示すクラス図は解の構造を明確にしています。
例えば、 PasteCommand クリップボードからテキストを貼り付けることをサポートしています 文書.受信機 PasteCommand は 文書 インスタンスに提供されるオブジェクトです。その 実行 インヴォークス作戦 ペースト 受領書類に記載。以下にスニペット図を見てみましょう。
OpenCommandの実行 操作は異なります。ユーザーにドキュメント名をプロンプトし、対応するドキュメントオブジェクトを作成し、そのドキュメントを受信アプリケーションに追加し、ドキュメントを開きます。以下にスニペット図を見てみましょう。
これらの例のそれぞれで、コマンドパターンが操作を呼び出すオブジェクトを、実行できる知識を持つオブジェクトから切り離していることに注目してください。
これにより、ユーザーインターフェースの設計に大きな柔軟性が生まれます。アプリケーションは、メニューとプッシュボタンを同じConcrete Commandサブクラスのインスタンスを共有するだけで、メニューとプッシュボタンインターフェースの両方を機能に提供できます。コマンドを動的に置き換えることができ、コンテキスト依存メニューの実装に役立ちます。コマンドを大きなコマンドにまとめることで、コマンドスクリプトもサポートできます。
これらすべては、リクエストを出すオブジェクトが発行方法を知っていれば十分だからです。依頼がどのように実行されるかを知る必要はありません。
では、コマンドパターンの実際の応用例を見てみましょう。 ダイナーの環境では、「オーダー」はコマンドデザインパターンの優れた実例となり得ます.Commandパターンを顧客の注文生成、料理の準備管理、ダイナーでの請求管理にどのように適用できるかを探ってみましょう。
これらはダイナー管理のためのコマンドパターンの構成要素です:
- コマンドインターフェース: コマンドインターフェースは、私たちの場合、どのコマンドでもサポートすべき共通操作を表しています 「実行」.
- コンクリートコマンド: Concrete CommandクラスはCommandインターフェースを実装し、注文に商品を追加する、注文から商品を削除するなど、顧客の注文に関連する特定のアクションをカプセル化します。
- レシーバー: レシーバーはコマンドの実行方法を知っているオブジェクトです。この場合、受信者は シェフ 誰が料理を準備しなきゃいけないのか、 レジ係 合計金額を計算しなければならない。
- インヴォーカー:インボーカーはコマンドの保存と実行を担当します。ダイナーの設定では、インヴォーカーは ウェイター注文を受け、シェフとレジ係に指示を伝えます。
以下は、ダイナーでの顧客注文管理に関するコマンドパターンの実装に関するシーケンス図の例です。
ウェイターやウェイトレスは顧客からの注文や指示を受け取り、その注文を小切手に書き記すことでカプセル化します。注文はキッチンに順に並べられます。シェフとレジ係が受取人であり、シェフが料理を準備し、レジ係が小切手を印刷します。
次に、Javaでのコード実装を見てみましょう。
例では、シェフの2人、「ステファノ」と「ルイサ」の存在を考慮しました。
ご覧の通り、私たちは注文管理の優先事項も考慮しています。例えば珍しい」 ステーキのために、そして「酢なしで「サラダのために。
この情報は、シェフが注文した料理の準備時に活用します。
また、キッチンにシェフを2人配置することも考えました。注文を取るウェイターが、その注文をどのシェフに渡すかを決めます。
コンソールの写真を見ると、プログラムは問題なく動作しているようです。
以下に 「メニューアイテム」 データクラスおよび 「コマンド」 パターンの基盤となるインターフェースと、さらに2つの具体的なコマンドも見られます。 「AddItemCommand」 e 「RemoveItemCommand」.
以下に、 「ウェイター」 そして 受信機 クラス 「チャシエ」 および 「シェフ」:
この時点で、コマンドパターン実装の2つの例を見た後、 「ツールキットメニュー」 および 「夕食の注文」指揮パターンの一般的な構造を詳細に検討する時が来ました。
まずは静的構造から始めましょう: クラス図.
コマンドパターンのクラス図には以下のコンポーネントが含まれています:
LinkedInのおすすめ
- 指揮: これはインターフェースや抽象クラスを宣言する中心コンポーネントであり、 実行() 命令を実行するための契約を定義するメソッド。
- コンクリートコマンド: Concrete Commandクラスは 指揮 特定のアクションおよびそれに対応する受信オブジェクトをインターフェースしカプセル化します。 具体的なコマンド さまざまな種類のリクエストを実装しましょう。具体的なコマンドは単独で作業を行うのではなく、ビジネスロジックオブジェクトのいずれかに呼び出しを渡すためのものです。受信オブジェクトに対してメソッドを実行するために必要なパラメータは、concreteコマンドのフィールドとして宣言できます。コマンドオブジェクトは、コンストラクタを通じてのみフィールドの初期化を許可することで不変にできます。
- クライアント: クライアントはコマンドオブジェクトを作成・設定します。クライアントは受信インスタンスを含むすべてのリクエストパラメータをコマンドのコンストラクタに渡さなければなりません。その後、得られるコマンドは1人または複数の送信者に関連付けられることがあります。
- インヴォーカー: 呼び出し者はコマンドを保存・実行し、クライアントをコマンド実行の詳細から抽象化します。インボーカーはリクエストを開始する責任を負います。このクラスにはコマンドオブジェクトへの参照を格納するフィールドが必要です。呼び出し者はそのコマンドの実行メソッドを呼び出し、リクエストを直接受信側に送るのではありません。送信側はコマンドオブジェクトの作成責任を負わないことに注意してください。通常、クライアントからコンストラクタを通じてあらかじめ作成されたコマンドを受け取ります。
- レシーバー: 受信者はコマンドに関連する操作の実行方法を知っています。リクエストを実行するのはターゲットオブジェクトです。
では、パターンの動的な挙動を見てみましょう。
動的オブジェクト協働は シーケンス図 下記はパターン成分間の相互作用が見える部分です。
- このサンプルシナリオでは、クライアントが インヴォーカー および 受信機 物。すると、 コンクリートコマンド オブジェクトを呼び出し者に渡します。
- 相互作用は インヴォーカー 呼び出すオブジェクト 実行() その上での方法 指揮 対象。
- その コンクリートコマンド オブジェクト呼び出し 操作() 方法 受信機 対象。
さて、パターンの構造を詳しく見たところで、パターンコマンドが非常に役立ついくつかのシナリオを見てみましょう。
例えば、 ユニバーサルリモコン テレビ、DVDプレーヤー、音響システムなど様々な電子機器を操作できます。リモコンには複数のボタンがあり、それぞれ異なる機能を表しています (例えば、オン・オフ・ボリュームアップ・ボリューム・下げ・チャンネル変更などです。).コマンドパターンはこの機能を効果的に実装するのに役立ちます。
動作するコンポーネントは常に同じです:
- インヴォーカーリモコン自体が呼び出し者です。各デバイスや機能の詳細は知らない。ただコマンドの実行方法を知っているだけです。
- コマンドリモコンの各ボタンはコマンドを表しています。これらのコマンドは、テレビの電源を入れたり音響システムの音量を上げたりといった特定の操作をまとめています。
- 受信機:電子機器 (テレビ、DVDプレーヤー、音響システム) 受信者です。コマンドが実行されたときに実際に行動する方法を知っています。
私はテレビオブジェクトとリモコンオブジェクトを組み合わせた概念的な例を書き、それをC++言語で書き、以下にソースコードを示します。
この例では、単純な テレビ受信機 および2つのコマンドオブジェクト: ターンオンコマンド および TurnOffCommand.その リモコン 次のように振る舞います。 インヴォーカー.リモコンのボタンを押すと、適切なコマンドが設定され、それを実行してテレビで対応するアクションが発動します。
そして、パターンコマンドを適用する別のシナリオを見てみましょう
で 同時編成コマンドデザインパターンは、マルチスレッドシナリオやタスクキューの管理に役立ちます。コマンドは、同時に実行する必要があるタスクや操作を表すことができます。呼び出し者はコマンドのキューを維持し、複数のスレッドがこれらのコマンドを同時に処理できるため、システムリソースの効率的な利用が可能です。
動作するコンポーネントは常に同じです:
- インヴォーカー: この文脈での呼び出し者はタスクスケジューラまたはスレッドプールマネージャーである場合があります。コマンドを実行する責任を負っています (任務) 正しい順序でスレッドを管理すること。
- コマンド: 各コマンドは、同時に実行したい特定のタスクや操作を表しています。これらのコマンドは、行うべき作業をカプセル化しています。
- 受信機: レシーバーはコマンドが実行された際に実際のタスクを実行するオブジェクトです。この例では、レシーバーがワーカースレッドである可能性があります。
コマンドを実行できるタスクキューがあるという概念的な例を書きました (任務) 同時にスレッドプールも使用しています。私はコトリン語で書きました。
この例では、 タスクキュー インヴォーカー それは スレッドプール 2つ ワーカースレッド.また、具体的なコマンドが2つあります。 タスク1 および タスク2それぞれが同時に実行すべき特定のタスクを表します。タスクをTaskQueueに追加すると、スレッドプールのワーカースレッドのいずれかが実行します。その ワーカースレッド クラスは実際のタスクを実行する受信者を表します。
コマンドパターンの分析を終える前に、コマンドデザインパターンと各SOLID原則との関係を検証しましょう (.2,.3.4.5.6):
- 単一責任原則 (SRP) (2.)SRPは、クラスが変化する理由は一つだけ、つまり単一の責任を持つべきだと述べています。コマンド設計パターンは、各コマンドを別々のクラスにカプセル化することで、この原則を守るのに役立ちます。各コマンドは特定のアクションを実行する責任を持ち、そのアクションに関連する変更はそれぞれのコマンドクラス内に限定されます。この隔離は、あるコマンドの変更が他のコマンドに影響を与えないため、保守性を高めます。
- 開閉原理 (OCP) (3.): OCPはソフトウェアの主体が (クラス、モジュール、関数) 延長は開いているはずですが、改造は閉じています。コマンドデザインパターンは、既存のクライアントコードを変更せずに新しいコマンドを追加できるという原則に従っています。クライアント、呼び出し、受信者のクラスを変更することなく、新しいConcrete Commandクラスを導入できるため、システムの拡張性を高めます。
- リスコフ置換原理 (LSP) (4.): LSPは、スーパークラスのオブジェクトはプログラムの正しさに影響を与えることなく、そのサブクラスのオブジェクトに置き換えられるべきだと述べています。コマンド設計パターンは、主にコマンドの実行とリクエストのカプセル化を扱うため、この原則に直接影響しません。しかし、パターンを通じてSRPとOCPを遵守することは、間接的にLSPの目標を支えます。
- インターフェース分離原則 (ISP) (5.): ISPは、クラスが使わないインターフェースを強制的に実装すべきではないと助言しています。コマンドデザインパターンの文脈では、この原則はパターン自体に明確に結びついていません。しかし、コマンドパターンを実装する際には、コマンド用のインターフェースまたは抽象クラスを定義します。このインターフェースには必要なメソッドのみが含まれるべきです (例: 実行())実装クラスが無関係なメソッドで肥大化することなく、特定の目的を持つことを保証します。
- 依存反転原理 (ディップ) (6.): DIPは高レベルモジュールが低レベルモジュールに依存してはいけないと提案しています。どちらも抽象化に基づいているはずです。コマンドデザインパターンは抽象化を導入することでこの原則を遵守できます (インターフェース) 命令のために。クライアントクラスとインボーカークラスはコマンドインターフェースに依存します (抽象化) 具体的なコマンドクラスではなく (実装).これにより、クライアントや呼び出しコードを変更することなく、異なるコマンドを簡単にシステムに挿入できる柔軟で交換可能なコマンド設定が可能になります。
以上です 「コマンドパターン」.
ニュースレターを思い出させてほしい「Sw Design & Clean Architecture」:https://lnkd.in/eUzYBuEX私の過去の記事をここでご覧いただけますし、まだ登録していなければ登録も可能です。新しい記事を公開すると通知が届きます。
私の記事を読んでいただきありがとうございます。このテーマがお役に立てば幸いです。
どんなフィードバックでも気軽に残してください。
ご意見をいただけると大変ありがたいです。
改めてありがとうございます。
スティーブン
参考文献:
1.ガンマ、ヘルム、ジョンソン、ヴリサイドズ、「デザインパターン」、アディソン・ウェズリー (2°版 2002年10月).199-207ページ。
2.S. サンティリ:「https://www.linkedin.com/pulse/single-responsibility-principle-stefano-santilli/」。
3.S. サンティリ:「https://www.linkedin.com/pulse/open-closed-principle-stefano-santilli/」。
4.S. サンティリ:「https://www.linkedin.com/pulse/squarerectangle-problem-stefano-santilli/」。
5.S. サンティリ:「https://www.linkedin.com/pulse/interface-segregation-principle-isp-stefano-santilli/」。
6.S. サンティリ:「https://www.linkedin.com/pulse/dependency-inversion-principle-stefano-santilli/」。
YemenSoft Ltd. Co.…•3576人のフォロワー
2年前Dear Stefano Santilli 🤩🌹, I would like to express my deep gratitude and heartfelt thanks for the outstanding article , I found it to be truly inspiring and immensely valuable. I greatly appreciated your writing style and the organization of the information. You provided clear examples and illustrative code snippets that made it easy to comprehend complex concepts. Additionally, your ability to highlight practical use cases and real-world scenarios for the design pattern helped solidify my understanding of the topic.