記事ディレクトリ
1. コマンドモードとは
コマンド パターン (コマンド パターン) はコマンドのカプセル化であり、各コマンドは操作です。要求側は操作を実行する要求を送信し、受信側は要求を受信して操作を実行します。コマンドモードでは、要求側と受信側が分離されており、要求側はコマンドの実行を要求するだけでよく、コマンドがどのように受け付けられ、どのように操作され、実行されるかは気にしません。コマンドパターンは行動パターンです。
原文: リクエストを医療オブジェクトにカプセル化し、さまざまなリクエストを使用してクライアントをパラメータ化し、リクエストをキューに入れたり、リクエスト ログを記録したり、コマンドのキャンセルおよび回復機能を提供したりできるようにします。
ソフトウェア システムでは、動作の要求者と動作の実装者は通常、緊密に結合された関係にあります。これは、そのような実装が単純かつ明確であるためです。ただし、密結合関係はスケーラビリティに欠けており、場合によっては、動作を記録したり、元に戻したり、やり直したりする必要がある場合、ソース コードを変更することしかできません。コマンドモードは、リクエストと実装の間に抽象コマンドインターフェイスを導入することでリクエストと実装を分離し、ミドルウェアは抽象的であり、異なるサブクラスで実装できるため拡張性があります。したがって、コマンド モードの本質は、コマンドの要求と処理を分離することです。
1. コマンドモードの使用シナリオ
システムの動作にコマンド セマンティクスがあり、コマンドの実装が不安定 (変化している) な場合、コマンド モードを通じてリクエストと実装を切り離すことができ、抽象コマンド インターフェイスを使用してリクエスタとコマンドのコード構造を安定させることができます。受信側の特定のコマンド実装の詳細をカプセル化します。レシーバーは抽象コマンド インターフェイスと弱く結合されており (内部メソッドが一貫している必要はありません)、優れたスケーラビリティを備えています。コマンド モードは、次のアプリケーション シナリオに適しています。
- システムは、呼び出し元と受信者が直接対話しないように、リクエストの呼び出し元とリクエストの受信者を切り離す必要があります。
- 実際のセマンティクスにおける「コマンド」を使用した操作 (コマンド メニュー、シェル コマンドなど)。
- システムは、コマンドの元に戻す (Undo) 操作と回復 (Redo) 操作をサポートする必要があります。
- システムはリクエストを指定し、キューに入れて、異なる時間に実行する必要があります。
- コマンド マクロ (つまり、コマンドの組み合わせ操作) をサポートする必要があります。
2. コマンドモードの主な役割
コマンド パターンは次の主な役割で構成されます。
- 抽象コマンドクラス(Command)の役割:コマンドのインターフェースを定義し、実行方法を宣言します。
- コンクリート コマンド (コンクリート コマンド) の役割: コマンド インターフェイスを実装する特定のコマンド。通常はレシーバーを保持し、コマンドによって実行される操作を完了するためにレシーバーの関数を呼び出します。
- Realizer/Receiver (受信者) の役割: 受信者、コマンドを実際に実行するオブジェクト。注文に必要な対応する機能を実現できるクラスであれば、どのクラスでも受信機となることができます。
- 呼び出し者/要求者 (Invoker) ロール: 要求を実行するにはコマンド オブジェクトが必要で、通常はコマンド オブジェクトを保持し、多くのコマンド オブジェクトを保持できます。これは、クライアントが実際にコマンドをトリガーし、コマンドが対応する操作を実行することを要求する場所です。つまり、コマンド オブジェクトを使用するエントリと同等です。
コマンド モードの UML クラス図からわかります。コマンドは、相互に分離された受信者と呼び出し者のミドルウェアとして表示されます。コマンドミドルウェアが導入される理由は、主に次の 2 つの理由によるものです。
(1) リクエストと実装の分離: つまり、UML クラス図では、呼び出し側は特定の実装であり、顧客のコマンドの受信を待機しているため、呼び出し側と受信側を分離します。は端末から渡されます (つまり、呼び出し側はクライアントに結合されています)。呼び出し側はビジネス ロジック領域内にあり、安定した構造である必要があります。Receiver はビジネス機能モジュールに属し、常に変化します。Command が存在しない場合、Invoker と Receiver は密結合になり、安定した構造が不安定な構造に依存するため、全体の構造が不安定になります。これが、コマンドが導入された理由です。リクエストと実装を分離するだけでなく、(呼び出し元) に依存するドキュメント (コマンド) を安定させるためでもあり、その構造は依然として安定しています。
(2) スケーラビリティの強化: スケーラビリティは 2 つの側面に反映されます: ① 受信機は基礎となる詳細に属し、異なる受信機を置き換えることによって異なる詳細を実現できます。 ② コマンド インターフェイス自体が抽象的でスケーラビリティを持ち、コマンド自体が抽象化されているため、デコレータ モードと組み合わせると、機能拡張は水を得た魚のようなものになります。
ノート!システムでは、さまざまなコマンドがさまざまなリクエストに対応します。つまり、リクエストを抽象化することはできないため、コマンド モードの Receiver は具象実装ですが、特定のモジュール内であれば、実際には Receiver を抽象化できます。これは偽装された形式です。ブリッジ モードが使用されているため (Command クラスには、Command と Receiver という 2 つの変化する次元があります)、スケーラビリティが向上します。
3. コマンドモードのメリットとデメリット
アドバンテージ:
- システムの結合を減らします。コマンド パターンは、操作を実行するオブジェクトから操作を呼び出すオブジェクトを分離します。
- コマンドの追加や削除はとても便利です。コマンド モードを使用してコマンドを追加および削除しても、他のクラスに影響を与えることはなく、「オープンとクローズの原則」を満たし、拡張がより柔軟になります。
- マクロコマンドを実装することができます。コマンド モードを組み合わせモードと組み合わせて、複数のコマンドを組み合わせて 1 つのコマンド、つまりマクロ コマンドにまとめることができます。
- 元に戻す操作とやり直し操作を実装すると便利です。コマンドモードとメモモードを組み合わせて、コマンドの無効化と復元を実現できます。
- 既存のコマンドに基づいて、追加の機能を追加できます(ロギングなど、デコレータ モードを組み合わせると効果が向上します)。
欠点:
- コマンド パターンを使用すると、一部のシステムで具体的なコマンド クラスが多すぎる場合があります。
- コマンド モードの結果は実際には受信者の実行結果ですが、コマンドの形式で構造を構造化し、リクエストと実装を分離するために、追加の型構造 (リクエスターと抽象コマンド インターフェイスの導入) が導入されます。これにより、理解が難しくなります (ただし、これはデザイン パターンによってもたらされる一般的な問題でもあり、抽象化では必然的に追加の型が導入されます。抽象化はコンパクトよりも明らかに理解しにくいです)。
- 使用頻度が低く、理解するのが難しく、非常に特殊なアプリケーション シナリオでのみ使用されます。
4. コマンドモードの注意事項と詳細
- リクエストを開始するオブジェクトを、リクエストを実行するオブジェクトから切り離します。リクエストを開始するオブジェクトは呼び出し元です。呼び出し元は、特定の受信側オブジェクトが誰で、どのように実装されているかを知らなくても、受信側を動作させるためにコマンド オブジェクトのexecute() メソッドを呼び出すだけで済みます。コマンド オブジェクトが責任を負います。アクション、つまり「リクエストの開始者」と「リクエストの実行者」の間の分離はコマンド オブジェクトを通じて実現され、コマンド オブジェクトはブリッジとして機能します。
- コマンド オブジェクトをキューに配置するだけで、コマンドを複数のスレッドで実行できるため、コマンド キューの設計が簡単です。
- リクエストを簡単に元に戻したりやり直したりできます。
空命令也是一种设计模式
を使用すると、null を判断する操作が省略されます。インターフェイス アダプター パターンを使用して、コマンド インターフェイスを空のクラスに適合させることができます。
2. 使用例
1. コマンドモードの一般的な書き込み方法
//抽象命令接口
public interface ICommand {
void execute();
}
//具体命令
public class ConcreteCommand implements ICommand {
// 直接创建接收者,不暴露给客户端
private Receiver mReceiver;
public ConcreteCommand(Receiver mReceiver) {
this.mReceiver = mReceiver;
}
public void execute() {
this.mReceiver.action();
}
}
//请求者
public class Invoker {
private ICommand mCmd;
public Invoker(ICommand cmd) {
this.mCmd = cmd;
}
public void action() {
this.mCmd.execute();
}
}
//接收者
public class Receiver {
public void action() {
System.out.println("执行具体操作");
}
}
public class Test {
public static void main(String[] args) {
ICommand cmd = new ConcreteCommand(new Receiver());
Invoker invoker = new Invoker(cmd);
invoker.action();
}
}
2. プレーヤー機能の例
プレーヤーを自分で開発する場合、プレーヤーには再生、プログレスバーのドラッグ、再生の停止、再生の一時停止の機能があり、プレーヤーを自分で操作する場合は、プレーヤーのメソッドを直接呼び出すのではなく、コントロールを通じてプレーヤー コアに指示を伝えるため、伝えられる特定の指示は個々のボタンにカプセル化されます。したがって、各ボタンはコマンドをカプセル化することと同じになります。コントロール バーは、ユーザーが送信した命令とプレーヤー コアが受信した命令の分離を実現するために使用されます。
// Receiver角色:播放器内核GPlayer
public class GPlayer {
public void play(){
System.out.println("正常播放");
}
public void speed(){
System.out.println("拖动进度条");
}
public void stop(){
System.out.println("停止播放");
}
public void pause(){
System.out.println("暂停播放");
}
}
// 抽象命令Command角色:命令接口
public interface IAction {
void execute();
}
// 具体命令角色
public class StopAction implements IAction {
private GPlayer gplayer;
public StopAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.stop();
}
}
public class PauseAction implements IAction {
private GPlayer gplayer;
public PauseAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.pause();
}
}
public class PlayAction implements IAction {
private GPlayer gplayer;
public PlayAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.play();
}
}
public class SpeedAction implements IAction {
private GPlayer gplayer;
public SpeedAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.speed();
}
}
// Invoker 角色
public class Controller {
private List<IAction> actions = new ArrayList<IAction>();
public void addAction(IAction action){
actions.add(action);
}
public void execute(IAction action){
action.execute();
}
public void executes(){
for (IAction action:actions) {
action.execute();
}
actions.clear();
}
}
public class Test {
public static void main(String[] args) {
GPlayer player = new GPlayer();
Controller controller = new Controller();
controller.execute(new PlayAction(player));
controller.addAction(new PauseAction(player));
controller.addAction(new PlayAction(player));
controller.addAction(new StopAction(player));
controller.addAction(new SpeedAction(player));
controller.executes();
}
}
3. リモコンケース
照明、扇風機、冷蔵庫、洗濯機、テレビなどのスマート家電を一式購入し、携帯電話にアプリをインストールするだけで家電を操作できるようになりました。
//创建命令接口
public interface Command {
//执行动作(操作)
public void execute();
//撤销动作(操作)
public void undo();
}
/**
* 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
* 其实,这样是一种设计模式, 可以省掉对空判断
*/
public class NoCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
// 电灯Receiver
public class LightReceiver {
public void on() {
System.out.println(" 电灯打开了.. ");
}
public void off() {
System.out.println(" 电灯关闭了.. ");
}
}
// 电灯具体命令类
public class LightOffCommand implements Command {
// 聚合LightReceiver
LightReceiver light;
// 构造器
public LightOffCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
// 调用接收者的方法
light.off();
}
@Override
public void undo() {
// 调用接收者的方法
light.on();
}
}
// 电灯具体命令类
public class LightOnCommand implements Command {
//聚合LightReceiver
LightReceiver light;
//构造器
public LightOnCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
//调用接收者的方法
light.on();
}
@Override
public void undo() {
//调用接收者的方法
light.off();
}
}
// 电视机Receiver
public class TVReceiver {
public void on() {
System.out.println(" 电视机打开了.. ");
}
public void off() {
System.out.println(" 电视机关闭了.. ");
}
}
// 电视机具体命令
public class TVOnCommand implements Command {
// 聚合TVReceiver
TVReceiver tv;
// 构造器
public TVOnCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
// 调用接收者的方法
tv.on();
}
@Override
public void undo() {
// 调用接收者的方法
tv.off();
}
}
public class TVOffCommand implements Command {
// 聚合TVReceiver
TVReceiver tv;
// 构造器
public TVOffCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
// 调用接收者的方法
tv.off();
}
@Override
public void undo() {
// 调用接收者的方法
tv.on();
}
}
// 控制器,Invoker角色
public class RemoteController {
// 开 按钮的命令数组
Command[] onCommands;
Command[] offCommands;
// 执行撤销的命令
Command undoCommand;
// 构造器,完成对按钮初始化
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
// 给我们的按钮设置你需要的命令
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
// 按下开按钮
public void onButtonWasPushed(int no) {
// no 0
// 找到你按下的开的按钮, 并调用对应方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = onCommands[no];
}
// 按下开按钮
public void offButtonWasPushed(int no) {
// no 0
// 找到你按下的关的按钮, 并调用对应方法
offCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = offCommands[no];
}
// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
//使用命令设计模式,完成通过遥控器,对电灯的操作
//创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//需要一个遥控器
RemoteController remoteController = new RemoteController();
//给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("--------按下灯的开按钮-----------");
remoteController.onButtonWasPushed(0);
System.out.println("--------按下灯的关按钮-----------");
remoteController.offButtonWasPushed(0);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
System.out.println("=========使用遥控器操作电视机==========");
TVReceiver tvReceiver = new TVReceiver();
TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);
//给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作
remoteController.setCommand(1, tvOnCommand, tvOffCommand);
System.out.println("--------按下电视机的开按钮-----------");
remoteController.onButtonWasPushed(1);
System.out.println("--------按下电视机的关按钮-----------");
remoteController.offButtonWasPushed(1);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
}
}
3. ソースコードのコマンドモード
1、糸
Runable は典型的なコマンド モードで、Runnable はコマンドとして機能し、Thread は呼び出し元として機能し、start メソッドはその実行メソッドです。
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
//调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
ネイティブ メソッド start0() を呼び出し、システム メソッドを呼び出して、スレッドを開始します。レシーバーはプログラマーに公開されており、プログラマーはレシーバーを自分で定義できます。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
*/
public class TurnOffThread implements Runnable{
private Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
}
/**
* 测试类
*/
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}
実際、スレッドの start メソッドを呼び出した後は、CPU リソースを取得するためのロジックを作成しなくても、CPU リソースを取得できるようになります。スレッドは CPU リソースを取得した後、run メソッドのコンテンツを実行し、Runnable インターフェイスを使用してユーザー リクエストを CPU 実行から切り離します。